Initial commit
This commit is contained in:
parent
1e90776b47
commit
e7891e1011
|
@ -0,0 +1,356 @@
|
|||
# PTMagic specific
|
||||
_[Dd]oc
|
||||
_[Ss]ource
|
||||
_[Ll]ogfiles
|
||||
_[Ll]og
|
||||
_[Ll]ogs
|
||||
_[Dd]ata
|
||||
_backups
|
||||
LocalProfitTrailer
|
||||
PTMagic/settings.*.json
|
||||
Monitor/appsettings.json
|
||||
/.vscode/tasks.json
|
||||
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/visualstudio,visualstudiocode,phpstorm
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
|
||||
### PhpStorm ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/dataSources/
|
||||
.idea/dataSources.ids
|
||||
.idea/dataSources.xml
|
||||
.idea/dataSources.local.xml
|
||||
.idea/sqlDataSources.xml
|
||||
.idea/dynamic.xml
|
||||
.idea/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/gradle.xml
|
||||
.idea/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
### PhpStorm Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
|
||||
### VisualStudio ###
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.vcxproj.filters
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/
|
||||
|
||||
### VisualStudio Patch ###
|
||||
build/
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "debug PTMagic",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "VS build PTMagic",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/PTMagic/bin/Debug/netcoreapp2.0/PTMagic.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/PTMagic",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug Monitor",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "VS build Monitor",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/Monitor/bin/Debug/netcoreapp2.0/Monitor.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/Monitor",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"launchBrowser": {
|
||||
"enabled": true,
|
||||
"args": "${auto-detect-url}",
|
||||
"windows": {
|
||||
"command": "cmd.exe",
|
||||
"args": "/C start ${auto-detect-url}"
|
||||
},
|
||||
"osx": {
|
||||
"command": "open"
|
||||
},
|
||||
"linux": {
|
||||
"command": "xdg-open"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
,]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.1-beta3" />
|
||||
<PackageReference Include="NLog" Version="4.5.0-rc04" />
|
||||
<PackageReference Include="NLog.Extensions.Logging" Version="1.0.0-rtm-rc6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.0.0-alpha2" />
|
||||
<PackageReference Include="Telegram.Bot" Version="14.0.0-rc-367" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,528 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
|
||||
namespace Core.Main.DataObjects.PTMagicData {
|
||||
#region Settings Objects
|
||||
public class GeneralSettingsWrapper {
|
||||
public GeneralSettings GeneralSettings { get; set; }
|
||||
}
|
||||
|
||||
public class AnalyzerSettingsWrapper {
|
||||
public AnalyzerSettings AnalyzerSettings { get; set; }
|
||||
}
|
||||
|
||||
public class SecureSettingsWrapper {
|
||||
public SecureSettings SecureSettings { get; set; }
|
||||
}
|
||||
|
||||
#region GeneralSettings
|
||||
public class GeneralSettings {
|
||||
public Application Application { get; set; }
|
||||
public Monitor Monitor { get; set; }
|
||||
public Backup Backup { get; set; }
|
||||
public Telegram Telegram { get; set; }
|
||||
}
|
||||
|
||||
public class Application {
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public bool TestMode { get; set; } = true;
|
||||
public bool EnableBetaFeatures { get; set; } = false;
|
||||
public int ProfitTrailerMajorVersion { get; set; } = 1;
|
||||
public string ProfitTrailerPath { get; set; }
|
||||
public string ProfitTrailerLicense { get; set; } = "";
|
||||
public string ProfitTrailerMonitorURL { get; set; } = "http://localhost:8081/";
|
||||
public string ProfitTrailerDefaultSettingName { get; set; } = "default";
|
||||
public bool AlwaysLoadDefaultBeforeSwitch { get; set; } = true;
|
||||
public int FloodProtectionMinutes { get; set; } = 15;
|
||||
public string Exchange { get; set; }
|
||||
public double StartBalance { get; set; } = 0;
|
||||
public string InstanceName { get; set; } = "PT Magic";
|
||||
public string TimezoneOffset { get; set; } = "+0:00";
|
||||
public string MainFiatCurrency { get; set; } = "USD";
|
||||
}
|
||||
|
||||
public class Monitor {
|
||||
private string _rootUrl = "/";
|
||||
|
||||
public bool IsPasswordProtected { get; set; } = true;
|
||||
public bool OpenBrowserOnStart { get; set; } = false;
|
||||
public int Port { get; set; } = 5000;
|
||||
public int GraphIntervalMinutes { get; set; } = 60;
|
||||
public int GraphMaxTimeframeHours { get; set; } = 24;
|
||||
public int RefreshSeconds { get; set; } = 30;
|
||||
public int BagAnalyzerRefreshSeconds { get; set; } = 5;
|
||||
public int BuyAnalyzerRefreshSeconds { get; set; } = 5;
|
||||
public int MaxTopMarkets { get; set; } = 20;
|
||||
public int MaxDailySummaries { get; set; } = 10;
|
||||
public int MaxMonthlySummaries { get; set; } = 10;
|
||||
public int MaxDashboardBuyEntries { get; set; } = 10;
|
||||
public int MaxDashboardBagEntries { get; set; } = 10;
|
||||
public int MaxDCAPairs { get; set; } = 24;
|
||||
public int MaxSettingsLogEntries { get; set; } = 20;
|
||||
public string LinkPlatform { get; set; } = "TradingView";
|
||||
public string DefaultDCAMode { get; set; } = "Simple";
|
||||
|
||||
public string RootUrl {
|
||||
get {
|
||||
if (!_rootUrl.EndsWith("/")) _rootUrl += "/";
|
||||
return _rootUrl;
|
||||
}
|
||||
set {
|
||||
_rootUrl = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Backup {
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public int MaxHours { get; set; } = 48;
|
||||
}
|
||||
|
||||
public class Telegram {
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
public string BotToken { get; set; }
|
||||
public Int64 ChatId { get; set; }
|
||||
public bool SilentMode { get; set; } = false;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AnalyzerSettings
|
||||
public class AnalyzerSettings {
|
||||
public MarketAnalyzer MarketAnalyzer { get; set; }
|
||||
public List<GlobalSetting> GlobalSettings { get; set; }
|
||||
public List<SingleMarketSetting> SingleMarketSettings { get; set; }
|
||||
}
|
||||
|
||||
public class MarketAnalyzer {
|
||||
public int StoreDataMaxHours { get; set; }
|
||||
public int IntervalMinutes { get; set; } = 5;
|
||||
public bool ExcludeMainCurrency { get; set; } = true;
|
||||
public List<MarketTrend> MarketTrends { get; set; }
|
||||
}
|
||||
|
||||
public class MarketTrend {
|
||||
public string Name { get; set; }
|
||||
public string Platform { get; set; } = "Exchange";
|
||||
|
||||
[DefaultValue("Market")]
|
||||
public string TrendCurrency { get; set; } = "Market";
|
||||
|
||||
[DefaultValue(0)]
|
||||
public int MaxMarkets { get; set; } = 0;
|
||||
public int TrendMinutes { get; set; } = 0;
|
||||
|
||||
[DefaultValue(true)]
|
||||
public bool DisplayGraph { get; set; } = true;
|
||||
|
||||
[DefaultValue(true)]
|
||||
public bool DisplayOnMarketAnalyzerList { get; set; } = true;
|
||||
|
||||
[DefaultValue("")]
|
||||
public string IgnoredMarkets { get; set; } = "";
|
||||
|
||||
[DefaultValue("")]
|
||||
public string AllowedMarkets { get; set; } = "";
|
||||
|
||||
[DefaultValue(true)]
|
||||
public bool ExcludeMainCurrency { get; set; } = true;
|
||||
}
|
||||
|
||||
public class GlobalSetting {
|
||||
public string SettingName { get; set; }
|
||||
public string TriggerConnection { get; set; } = "AND";
|
||||
public List<Trigger> Triggers { get; set; } = new List<Trigger>();
|
||||
public Dictionary<string, object> PairsProperties { get; set; } = new Dictionary<string, object>();
|
||||
public Dictionary<string, object> DCAProperties { get; set; } = new Dictionary<string, object>();
|
||||
public Dictionary<string, object> IndicatorsProperties { get; set; } = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public class SingleMarketSetting {
|
||||
public string SettingName { get; set; }
|
||||
public string TriggerConnection { get; set; } = "AND";
|
||||
|
||||
[DefaultValue("AND")]
|
||||
public string OffTriggerConnection { get; set; } = "AND";
|
||||
|
||||
[DefaultValue(true)]
|
||||
public bool RefreshOffTriggers { get; set; } = true;
|
||||
|
||||
[DefaultValue("")]
|
||||
public string IgnoredMarkets { get; set; } = "";
|
||||
|
||||
[DefaultValue("")]
|
||||
public string AllowedMarkets { get; set; } = "";
|
||||
|
||||
[DefaultValue("")]
|
||||
public string IgnoredGlobalSettings { get; set; } = "";
|
||||
|
||||
[DefaultValue("")]
|
||||
public string AllowedGlobalSettings { get; set; } = "";
|
||||
|
||||
[DefaultValue(false)]
|
||||
public bool StopProcessWhenTriggered { get; set; } = false;
|
||||
public List<Trigger> Triggers { get; set; } = new List<Trigger>();
|
||||
public List<OffTrigger> OffTriggers { get; set; } = new List<OffTrigger>();
|
||||
public Dictionary<string, object> PairsProperties { get; set; } = new Dictionary<string, object>();
|
||||
public Dictionary<string, object> DCAProperties { get; set; } = new Dictionary<string, object>();
|
||||
public Dictionary<string, object> IndicatorsProperties { get; set; } = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public class Trigger {
|
||||
[DefaultValue("")]
|
||||
public string MarketTrendName { get; set; } = "";
|
||||
|
||||
[DefaultValue("Relative")]
|
||||
public string MarketTrendRelation { get; set; } = "Relative";
|
||||
|
||||
[DefaultValue(Constants.MaxTrendChange)]
|
||||
public double MaxChange { get; set; } = Constants.MaxTrendChange;
|
||||
|
||||
[DefaultValue(Constants.MinTrendChange)]
|
||||
public double MinChange { get; set; } = Constants.MinTrendChange;
|
||||
|
||||
[DefaultValue(Constants.Max24hVolume)]
|
||||
public double Max24hVolume { get; set; } = Constants.Max24hVolume;
|
||||
|
||||
[DefaultValue(0.0)]
|
||||
public double Min24hVolume { get; set; } = 0.0;
|
||||
|
||||
[DefaultValue(0)]
|
||||
public int AgeDaysLowerThan { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class OffTrigger {
|
||||
[DefaultValue("")]
|
||||
public string MarketTrendName { get; set; } = "";
|
||||
|
||||
[DefaultValue("Relative")]
|
||||
public string MarketTrendRelation { get; set; } = "Relative";
|
||||
|
||||
[DefaultValue(Constants.MaxTrendChange)]
|
||||
public double MaxChange { get; set; } = Constants.MaxTrendChange;
|
||||
|
||||
[DefaultValue(Constants.MinTrendChange)]
|
||||
public double MinChange { get; set; } = Constants.MinTrendChange;
|
||||
|
||||
[DefaultValue(Constants.Max24hVolume)]
|
||||
public double Max24hVolume { get; set; } = Constants.Max24hVolume;
|
||||
|
||||
[DefaultValue(0.0)]
|
||||
public double Min24hVolume { get; set; } = 0.0;
|
||||
|
||||
[DefaultValue(0)]
|
||||
public int HoursSinceTriggered { get; set; } = 0;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SecureSettings
|
||||
public class SecureSettings {
|
||||
public string MonitorPassword { get; set; } = "";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Market Analyzer Objects
|
||||
public class Market {
|
||||
public int Position;
|
||||
public string Name = "";
|
||||
public string Symbol = "";
|
||||
public double Volume24h = 0.0;
|
||||
public double Price = 0.0;
|
||||
public double TrendChange24h = 0.0;
|
||||
public double MainCurrencyPriceUSD = 0.0;
|
||||
}
|
||||
|
||||
public class MarketTick {
|
||||
public double Volume24h = 0.0;
|
||||
public double Price = 0.0;
|
||||
public DateTime Time = Constants.confMinDate;
|
||||
}
|
||||
|
||||
public class MarketTrendChange {
|
||||
public string MarketTrendName = "";
|
||||
public string Market = "";
|
||||
public double LastPrice = 0.0;
|
||||
public double Volume24h = 0.0;
|
||||
public double TrendMinutes = 0.0;
|
||||
public double TrendChange = 0.0;
|
||||
public DateTime TrendDateTime = Constants.confMinDate;
|
||||
}
|
||||
|
||||
public class MarketInfo {
|
||||
public string Name = "";
|
||||
public DateTime FirstSeen = Constants.confMinDate;
|
||||
public DateTime LastSeen = Constants.confMaxDate;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Summary Objects
|
||||
public class Summary {
|
||||
public string Version { get; set; } = "";
|
||||
public DateTime LastRuntime { get; set; } = Constants.confMinDate;
|
||||
public int LastRuntimeSeconds { get; set; } = 0;
|
||||
public DateTime LastGlobalSettingSwitch { get; set; } = Constants.confMinDate;
|
||||
public GlobalSetting CurrentGlobalSetting { get; set; } = null;
|
||||
public GlobalSetting FloodProtectedSetting { get; set; } = null;
|
||||
public bool IsSOMActive { get; set; } = false;
|
||||
public Dictionary<string, MarketPairSummary> MarketSummary { get; set; } = new Dictionary<string, MarketPairSummary>();
|
||||
public Dictionary<string, List<MarketTrendChange>> MarketTrendChanges { get; set; } = new Dictionary<string, List<MarketTrendChange>>();
|
||||
public List<GlobalSettingSummary> GlobalSettingSummary { get; set; } = new List<DataObjects.PTMagicData.GlobalSettingSummary>();
|
||||
public double BuyValue { get; set; } = 0;
|
||||
public double TrailingBuy { get; set; } = 0;
|
||||
public double SellValue { get; set; } = 0;
|
||||
public double TrailingProfit { get; set; } = 0;
|
||||
public double MaxTradingPairs { get; set; } = 0;
|
||||
public double MaxCost { get; set; } = 0;
|
||||
public double MaxCostPercentage { get; set; } = 0;
|
||||
public double MinBuyVolume { get; set; } = 0;
|
||||
public double DCALevels { get; set; } = 0;
|
||||
public double DCATrigger { get; set; } = 0;
|
||||
public Dictionary<int, double> DCATriggers { get; set; } = new Dictionary<int, double>();
|
||||
public double DCAPercentage { get; set; } = 0;
|
||||
public Dictionary<int, double> DCAPercentages { get; set; } = new Dictionary<int, double>();
|
||||
public string DCABuyStrategy { get; set; } = "";
|
||||
public string BuyStrategy { get; set; } = "";
|
||||
public string SellStrategy { get; set; } = "";
|
||||
public string MainMarket { get; set; } = "";
|
||||
public double MainMarketPrice { get; set; } = 0;
|
||||
public string MainFiatCurrency { get; set; } = "USD";
|
||||
public double MainFiatCurrencyExchangeRate { get; set; } = 1;
|
||||
public int ProfitTrailerMajorVersion { get; set; } = 1;
|
||||
public List<StrategySummary> BuyStrategies { get; set; } = new List<StrategySummary>();
|
||||
public List<StrategySummary> SellStrategies { get; set; } = new List<StrategySummary>();
|
||||
public List<StrategySummary> DCABuyStrategies { get; set; } = new List<StrategySummary>();
|
||||
public List<StrategySummary> DCASellStrategies { get; set; } = new List<StrategySummary>();
|
||||
}
|
||||
|
||||
public class StrategySummary {
|
||||
public string Name { get; set; } = "";
|
||||
public double Value { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class GlobalSettingSummary {
|
||||
public string SettingName { get; set; }
|
||||
public DateTime SwitchDateTime { get; set; }
|
||||
public int ActiveSeconds { get; set; } = 0;
|
||||
public Dictionary<string, MarketTrendChange> MarketTrendChanges { get; set; } = new Dictionary<string, MarketTrendChange>();
|
||||
}
|
||||
|
||||
public class MarketPairSummary {
|
||||
public bool IsTradingEnabled { get; set; } = false;
|
||||
public bool IsSOMActive { get; set; } = false;
|
||||
public bool IsDCAEnabled { get; set; } = false;
|
||||
public List<SingleMarketSetting> ActiveSingleSettings { get; set; } = null;
|
||||
public double CurrentBuyValue { get; set; } = 0;
|
||||
public double CurrentTrailingBuy { get; set; } = 0;
|
||||
public double CurrentSellValue { get; set; } = 0;
|
||||
public double CurrentTrailingProfit { get; set; } = 0;
|
||||
public double LatestPrice { get; set; } = 0;
|
||||
public double Latest24hVolume { get; set; } = 0;
|
||||
public Dictionary<string, double> MarketTrendChanges { get; set; } = new Dictionary<string, double>();
|
||||
public List<StrategySummary> BuyStrategies { get; set; } = new List<StrategySummary>();
|
||||
public List<StrategySummary> SellStrategies { get; set; } = new List<StrategySummary>();
|
||||
public List<StrategySummary> DCABuyStrategies { get; set; } = new List<StrategySummary>();
|
||||
public List<StrategySummary> DCASellStrategies { get; set; } = new List<StrategySummary>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Transaction Objects
|
||||
public class Transaction {
|
||||
public string GUID { get; set; } = "";
|
||||
public DateTime UTCDateTime { get; set; } = Constants.confMinDate;
|
||||
public double Amount { get; set; } = 0.0;
|
||||
|
||||
public DateTime GetLocalDateTime(string offset) {
|
||||
DateTimeOffset result = this.UTCDateTime;
|
||||
|
||||
// Convert UTC sales time to local offset time
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(offset.Replace("+", ""));
|
||||
result = result.ToOffset(offsetTimeSpan);
|
||||
|
||||
return result.DateTime;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SingleMarketSettingSummary Objects
|
||||
public class SingleMarketSettingSummary {
|
||||
public string Market { get; set; } = "";
|
||||
public DateTime ActivationDateTimeUTC { get; set; } = Constants.confMinDate;
|
||||
public SingleMarketSetting SingleMarketSetting { get; set; } = null;
|
||||
public TriggerSnapshot TriggerSnapshot { get; set; } = null;
|
||||
}
|
||||
|
||||
public class TriggerSnapshot {
|
||||
public Dictionary<int, double> RelevantTriggers { get; set; } = new Dictionary<int, double>();
|
||||
public List<string> MatchedTriggersContent { get; set; } = new List<string>();
|
||||
public double LastPrice { get; set; } = 0;
|
||||
public double Last24hVolume { get; set; } = 0;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Profit Trailer JSON Objects
|
||||
|
||||
public class PTData {
|
||||
public List<sellLogData> SellLogData { get; set; } = new List<sellLogData>();
|
||||
public List<dcaLogData> DCALogData { get; set; } = new List<dcaLogData>();
|
||||
public List<dcaLogData> GainLogData { get; set; } = new List<dcaLogData>();
|
||||
public List<buyLogData> bbBuyLogData { get; set; } = new List<buyLogData>();
|
||||
}
|
||||
|
||||
public class sellLogData {
|
||||
public double soldAmount { get; set; }
|
||||
public SoldDate soldDate { get; set; }
|
||||
public int boughtTimes { get; set; }
|
||||
public string market { get; set; }
|
||||
public double profit { get; set; }
|
||||
public AverageCalculator averageCalculator { get; set; }
|
||||
public double currentPrice { get; set; }
|
||||
}
|
||||
|
||||
public class SellLogData {
|
||||
public double SoldAmount { get; set; }
|
||||
public DateTime SoldDate { get; set; }
|
||||
public int BoughtTimes { get; set; }
|
||||
public string Market { get; set; }
|
||||
public double ProfitPercent { get; set; }
|
||||
public double Profit { get; set; }
|
||||
public double AverageBuyPrice { get; set; }
|
||||
public double TotalCost { get; set; }
|
||||
public double SoldPrice { get; set; }
|
||||
public double SoldValue { get; set; }
|
||||
}
|
||||
|
||||
public class SoldDate {
|
||||
public Date date { get; set; }
|
||||
public Time time { get; set; }
|
||||
}
|
||||
|
||||
public class FirstBoughtDate {
|
||||
public Date date { get; set; }
|
||||
public Time time { get; set; }
|
||||
}
|
||||
|
||||
public class Date {
|
||||
public int year { get; set; }
|
||||
public int month { get; set; }
|
||||
public int day { get; set; }
|
||||
}
|
||||
|
||||
public class Time {
|
||||
public int hour { get; set; }
|
||||
public int minute { get; set; }
|
||||
public int second { get; set; }
|
||||
public int nano { get; set; }
|
||||
}
|
||||
|
||||
public class AverageCalculator {
|
||||
public double totalCost { get; set; }
|
||||
public double totalAmount { get; set; }
|
||||
public double totalAmountWithSold { get; set; }
|
||||
public double avgPrice { get; set; }
|
||||
public double avgCost { get; set; }
|
||||
public FirstBoughtDate firstBoughtDate { get; set; }
|
||||
public double totalWeightedPrice { get; set; }
|
||||
public double orderNumber { get; set; }
|
||||
public double fee { get; set; }
|
||||
}
|
||||
|
||||
public class PTStrategy {
|
||||
public string type { get; set; }
|
||||
public string name { get; set; }
|
||||
public double entryValue { get; set; }
|
||||
public double entryValueLimit { get; set; }
|
||||
public double triggerValue { get; set; }
|
||||
public double currentValue { get; set; }
|
||||
public double currentValuePercentage { get; set; }
|
||||
public int decimals { get; set; }
|
||||
public string positive { get; set; }
|
||||
}
|
||||
|
||||
public class dcaLogData {
|
||||
public int boughtTimes { get; set; } = 0;
|
||||
public string market { get; set; }
|
||||
public string positive { get; set; }
|
||||
public double buyProfit { get; set; }
|
||||
public double BBLow { get; set; }
|
||||
public double BBTrigger { get; set; }
|
||||
public double highbb { get; set; }
|
||||
public double profit { get; set; }
|
||||
public AverageCalculator averageCalculator { get; set; }
|
||||
public double currentPrice { get; set; }
|
||||
public string sellStrategy { get; set; }
|
||||
public string buyStrategy { get; set; }
|
||||
public double triggerValue { get; set; }
|
||||
public double percChange { get; set; }
|
||||
public List<PTStrategy> buyStrategies { get; set; }
|
||||
public List<PTStrategy> sellStrategies { get; set; }
|
||||
}
|
||||
|
||||
public class Strategy {
|
||||
public string Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
public double EntryValue { get; set; }
|
||||
public double EntryValueLimit { get; set; }
|
||||
public double TriggerValue { get; set; }
|
||||
public double CurrentValue { get; set; }
|
||||
public double CurrentValuePercentage { get; set; }
|
||||
public int Decimals { get; set; }
|
||||
public bool IsTrailing { get; set; }
|
||||
}
|
||||
|
||||
public class DCALogData {
|
||||
public int BoughtTimes { get; set; }
|
||||
public double CurrentLowBBValue { get; set; }
|
||||
public double CurrentHighBBValue { get; set; }
|
||||
public double BBTrigger { get; set; }
|
||||
public double BuyTriggerPercent { get; set; }
|
||||
public bool IsTrailing { get; set; }
|
||||
public string Market { get; set; }
|
||||
public double ProfitPercent { get; set; }
|
||||
public double AverageBuyPrice { get; set; }
|
||||
public double TotalCost { get; set; }
|
||||
public double Amount { get; set; }
|
||||
public double CurrentPrice { get; set; }
|
||||
public double SellTrigger { get; set; }
|
||||
public double PercChange { get; set; }
|
||||
public DateTime FirstBoughtDate { get; set; }
|
||||
public string SellStrategy { get; set; }
|
||||
public string BuyStrategy { get; set; }
|
||||
public List<Strategy> BuyStrategies { get; set; } = new List<Strategy>();
|
||||
public List<Strategy> SellStrategies { get; set; } = new List<Strategy>();
|
||||
}
|
||||
|
||||
public class buyLogData {
|
||||
public string market { get; set; }
|
||||
public string positive { get; set; }
|
||||
public double BBLow { get; set; }
|
||||
public double BBHigh { get; set; }
|
||||
public double BBTrigger { get; set; }
|
||||
public double profit { get; set; }
|
||||
public double currentPrice { get; set; }
|
||||
public double currentValue { get; set; }
|
||||
public string buyStrategy { get; set; }
|
||||
public double triggerValue { get; set; }
|
||||
public double percChange { get; set; }
|
||||
public List<PTStrategy> buyStrategies { get; set; }
|
||||
}
|
||||
|
||||
public class BuyLogData {
|
||||
public double CurrentLowBBValue { get; set; }
|
||||
public double CurrentHighBBValue { get; set; }
|
||||
public double BBTrigger { get; set; }
|
||||
public bool IsTrailing { get; set; }
|
||||
public string Market { get; set; }
|
||||
public double ProfitPercent { get; set; }
|
||||
public double CurrentPrice { get; set; }
|
||||
public double CurrentValue { get; set; }
|
||||
public double TriggerValue { get; set; }
|
||||
public double PercChange { get; set; }
|
||||
public string BuyStrategy { get; set; }
|
||||
public List<Strategy> BuyStrategies { get; set; } = new List<Strategy>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Core.Main.DataObjects {
|
||||
|
||||
public class ProfitTrailerData {
|
||||
private List<SellLogData> _sellLog = new List<SellLogData>();
|
||||
private List<DCALogData> _dcaLog = new List<DCALogData>();
|
||||
private List<BuyLogData> _buyLog = new List<BuyLogData>();
|
||||
private string _ptmBasePath = "";
|
||||
private PTMagicConfiguration _systemConfiguration = null;
|
||||
private TransactionData _transactionData = null;
|
||||
private DateTimeOffset _dateTimeNow = Constants.confMinDate;
|
||||
|
||||
public ProfitTrailerData(string ptmBasePath, PTMagicConfiguration systemConfiguration) {
|
||||
_ptmBasePath = ptmBasePath;
|
||||
_systemConfiguration = systemConfiguration;
|
||||
|
||||
PTData rawPTData = JsonConvert.DeserializeObject<PTData>(File.ReadAllText(systemConfiguration.GeneralSettings.Application.ProfitTrailerPath + "ProfitTrailerData.json"));
|
||||
if (rawPTData.SellLogData != null) {
|
||||
this.BuildSellLogData(rawPTData.SellLogData, _systemConfiguration);
|
||||
}
|
||||
|
||||
if (rawPTData.bbBuyLogData != null) {
|
||||
this.BuildBuyLogData(rawPTData.bbBuyLogData, _systemConfiguration);
|
||||
}
|
||||
|
||||
if (rawPTData.DCALogData != null) {
|
||||
this.BuildDCALogData(rawPTData.DCALogData, rawPTData.GainLogData, _systemConfiguration);
|
||||
}
|
||||
|
||||
// Convert local offset time to UTC
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
_dateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
|
||||
}
|
||||
|
||||
public List<SellLogData> SellLog {
|
||||
get {
|
||||
return _sellLog;
|
||||
}
|
||||
}
|
||||
|
||||
public List<SellLogData> SellLogToday {
|
||||
get {
|
||||
return _sellLog.FindAll(sl => sl.SoldDate.Date == _dateTimeNow.DateTime.Date);
|
||||
}
|
||||
}
|
||||
|
||||
public List<SellLogData> SellLogYesterday {
|
||||
get {
|
||||
return _sellLog.FindAll(sl => sl.SoldDate.Date == _dateTimeNow.DateTime.AddDays(-1).Date);
|
||||
}
|
||||
}
|
||||
|
||||
public List<SellLogData> SellLogLast7Days {
|
||||
get {
|
||||
return _sellLog.FindAll(sl => sl.SoldDate.Date >= _dateTimeNow.DateTime.AddDays(-7).Date);
|
||||
}
|
||||
}
|
||||
|
||||
public List<DCALogData> DCALog {
|
||||
get {
|
||||
return _dcaLog;
|
||||
}
|
||||
}
|
||||
|
||||
public List<BuyLogData> BuyLog {
|
||||
get {
|
||||
return _buyLog;
|
||||
}
|
||||
}
|
||||
|
||||
public TransactionData TransactionData {
|
||||
get {
|
||||
if (_transactionData == null) _transactionData = new TransactionData(_ptmBasePath);
|
||||
return _transactionData;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetCurrentBalance() {
|
||||
return this.GetSnapshotBalance(DateTime.Now.ToUniversalTime());
|
||||
}
|
||||
|
||||
public double GetSnapshotBalance(DateTime snapshotDateTime) {
|
||||
double result = _systemConfiguration.GeneralSettings.Application.StartBalance;
|
||||
|
||||
result += this.SellLog.FindAll(sl => sl.SoldDate.Date < snapshotDateTime.Date).Sum(sl => sl.Profit);
|
||||
result += this.TransactionData.Transactions.FindAll(t => t.GetLocalDateTime(_systemConfiguration.GeneralSettings.Application.TimezoneOffset) < snapshotDateTime).Sum(t => t.Amount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void BuildSellLogData(List<sellLogData> rawSellLogData, PTMagicConfiguration systemConfiguration) {
|
||||
foreach (sellLogData rsld in rawSellLogData) {
|
||||
SellLogData sellLogData = new SellLogData();
|
||||
sellLogData.SoldAmount = rsld.soldAmount;
|
||||
sellLogData.BoughtTimes = rsld.boughtTimes;
|
||||
sellLogData.Market = rsld.market;
|
||||
sellLogData.ProfitPercent = rsld.profit;
|
||||
sellLogData.SoldPrice = rsld.currentPrice;
|
||||
sellLogData.AverageBuyPrice = rsld.averageCalculator.avgPrice;
|
||||
sellLogData.TotalCost = sellLogData.SoldAmount * sellLogData.AverageBuyPrice;
|
||||
|
||||
double soldValueRaw = (sellLogData.SoldAmount * sellLogData.SoldPrice);
|
||||
double soldValueAfterFees = soldValueRaw - (soldValueRaw * (rsld.averageCalculator.fee / 100));
|
||||
sellLogData.SoldValue = soldValueAfterFees;
|
||||
sellLogData.Profit = Math.Round(sellLogData.SoldValue - sellLogData.TotalCost, 8);
|
||||
|
||||
// Profit Trailer sales are saved in UTC
|
||||
DateTimeOffset ptSoldDate = DateTimeOffset.Parse(rsld.soldDate.date.year.ToString() + "-" + rsld.soldDate.date.month.ToString("00") + "-" + rsld.soldDate.date.day.ToString("00") + "T" + rsld.soldDate.time.hour.ToString("00") + ":" + rsld.soldDate.time.minute.ToString("00") + ":" + rsld.soldDate.time.second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
|
||||
|
||||
// Convert UTC sales time to local offset time
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
ptSoldDate = ptSoldDate.ToOffset(offsetTimeSpan);
|
||||
|
||||
sellLogData.SoldDate = ptSoldDate.DateTime;
|
||||
|
||||
_sellLog.Add(sellLogData);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildDCALogData(List<dcaLogData> rawDCALogData, List<dcaLogData> rawPairsLogData, PTMagicConfiguration systemConfiguration) {
|
||||
foreach (dcaLogData rdld in rawDCALogData) {
|
||||
DCALogData dcaLogData = new DCALogData();
|
||||
dcaLogData.Amount = rdld.averageCalculator.totalAmount;
|
||||
dcaLogData.BoughtTimes = rdld.boughtTimes;
|
||||
dcaLogData.Market = rdld.market;
|
||||
dcaLogData.ProfitPercent = rdld.profit;
|
||||
dcaLogData.AverageBuyPrice = rdld.averageCalculator.avgPrice;
|
||||
dcaLogData.TotalCost = rdld.averageCalculator.totalCost;
|
||||
dcaLogData.BuyTriggerPercent = rdld.buyProfit;
|
||||
dcaLogData.CurrentLowBBValue = rdld.BBLow;
|
||||
dcaLogData.CurrentHighBBValue = rdld.highbb;
|
||||
dcaLogData.BBTrigger = rdld.BBTrigger;
|
||||
dcaLogData.CurrentPrice = rdld.currentPrice;
|
||||
dcaLogData.SellTrigger = rdld.triggerValue;
|
||||
dcaLogData.PercChange = rdld.percChange;
|
||||
dcaLogData.BuyStrategy = rdld.buyStrategy;
|
||||
if (dcaLogData.BuyStrategy == null) dcaLogData.BuyStrategy = "";
|
||||
dcaLogData.SellStrategy = rdld.sellStrategy;
|
||||
if (dcaLogData.SellStrategy == null) dcaLogData.SellStrategy = "";
|
||||
|
||||
if (rdld.positive != null) {
|
||||
dcaLogData.IsTrailing = rdld.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
} else {
|
||||
if (rdld.buyStrategies != null) {
|
||||
foreach (PTStrategy bs in rdld.buyStrategies) {
|
||||
Strategy buyStrategy = new Strategy();
|
||||
buyStrategy.Type = bs.type;
|
||||
buyStrategy.Name = bs.name;
|
||||
buyStrategy.EntryValue = bs.entryValue;
|
||||
buyStrategy.EntryValueLimit = bs.entryValueLimit;
|
||||
buyStrategy.TriggerValue = bs.triggerValue;
|
||||
buyStrategy.CurrentValue = bs.currentValue;
|
||||
buyStrategy.CurrentValuePercentage = bs.currentValuePercentage;
|
||||
buyStrategy.Decimals = bs.decimals;
|
||||
buyStrategy.IsTrailing = bs.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
|
||||
dcaLogData.BuyStrategies.Add(buyStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
if (rdld.sellStrategies != null) {
|
||||
foreach (PTStrategy ss in rdld.sellStrategies) {
|
||||
Strategy sellStrategy = new Strategy();
|
||||
sellStrategy.Type = ss.type;
|
||||
sellStrategy.Name = ss.name;
|
||||
sellStrategy.EntryValue = ss.entryValue;
|
||||
sellStrategy.EntryValueLimit = ss.entryValueLimit;
|
||||
sellStrategy.TriggerValue = ss.triggerValue;
|
||||
sellStrategy.CurrentValue = ss.currentValue;
|
||||
sellStrategy.CurrentValuePercentage = ss.currentValuePercentage;
|
||||
sellStrategy.Decimals = ss.decimals;
|
||||
sellStrategy.IsTrailing = ss.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
|
||||
dcaLogData.SellStrategies.Add(sellStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Profit Trailer bought times are saved in UTC
|
||||
if (rdld.averageCalculator.firstBoughtDate != null) {
|
||||
DateTimeOffset ptFirstBoughtDate = DateTimeOffset.Parse(rdld.averageCalculator.firstBoughtDate.date.year.ToString() + "-" + rdld.averageCalculator.firstBoughtDate.date.month.ToString("00") + "-" + rdld.averageCalculator.firstBoughtDate.date.day.ToString("00") + "T" + rdld.averageCalculator.firstBoughtDate.time.hour.ToString("00") + ":" + rdld.averageCalculator.firstBoughtDate.time.minute.ToString("00") + ":" + rdld.averageCalculator.firstBoughtDate.time.second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
|
||||
|
||||
// Convert UTC bought time to local offset time
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
ptFirstBoughtDate = ptFirstBoughtDate.ToOffset(offsetTimeSpan);
|
||||
|
||||
dcaLogData.FirstBoughtDate = ptFirstBoughtDate.DateTime;
|
||||
} else {
|
||||
dcaLogData.FirstBoughtDate = Constants.confMinDate;
|
||||
}
|
||||
|
||||
_dcaLog.Add(dcaLogData);
|
||||
}
|
||||
|
||||
foreach (dcaLogData rpld in rawPairsLogData) {
|
||||
DCALogData dcaLogData = new DCALogData();
|
||||
dcaLogData.Amount = rpld.averageCalculator.totalAmount;
|
||||
dcaLogData.BoughtTimes = 0;
|
||||
dcaLogData.Market = rpld.market;
|
||||
dcaLogData.ProfitPercent = rpld.profit;
|
||||
dcaLogData.AverageBuyPrice = rpld.averageCalculator.avgPrice;
|
||||
dcaLogData.TotalCost = rpld.averageCalculator.totalCost;
|
||||
dcaLogData.BuyTriggerPercent = rpld.buyProfit;
|
||||
dcaLogData.CurrentPrice = rpld.currentPrice;
|
||||
dcaLogData.SellTrigger = rpld.triggerValue;
|
||||
dcaLogData.PercChange = rpld.percChange;
|
||||
dcaLogData.BuyStrategy = rpld.buyStrategy;
|
||||
if (dcaLogData.BuyStrategy == null) dcaLogData.BuyStrategy = "";
|
||||
dcaLogData.SellStrategy = rpld.sellStrategy;
|
||||
if (dcaLogData.SellStrategy == null) dcaLogData.SellStrategy = "";
|
||||
dcaLogData.IsTrailing = false;
|
||||
|
||||
if (rpld.sellStrategies != null) {
|
||||
foreach (PTStrategy ss in rpld.sellStrategies) {
|
||||
Strategy sellStrategy = new Strategy();
|
||||
sellStrategy.Type = ss.type;
|
||||
sellStrategy.Name = ss.name;
|
||||
sellStrategy.EntryValue = ss.entryValue;
|
||||
sellStrategy.EntryValueLimit = ss.entryValueLimit;
|
||||
sellStrategy.TriggerValue = ss.triggerValue;
|
||||
sellStrategy.CurrentValue = ss.currentValue;
|
||||
sellStrategy.CurrentValuePercentage = ss.currentValuePercentage;
|
||||
sellStrategy.Decimals = ss.decimals;
|
||||
sellStrategy.IsTrailing = ss.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
|
||||
dcaLogData.SellStrategies.Add(sellStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
// Profit Trailer bought times are saved in UTC
|
||||
if (rpld.averageCalculator.firstBoughtDate != null) {
|
||||
DateTimeOffset ptFirstBoughtDate = DateTimeOffset.Parse(rpld.averageCalculator.firstBoughtDate.date.year.ToString() + "-" + rpld.averageCalculator.firstBoughtDate.date.month.ToString("00") + "-" + rpld.averageCalculator.firstBoughtDate.date.day.ToString("00") + "T" + rpld.averageCalculator.firstBoughtDate.time.hour.ToString("00") + ":" + rpld.averageCalculator.firstBoughtDate.time.minute.ToString("00") + ":" + rpld.averageCalculator.firstBoughtDate.time.second.ToString("00"), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
|
||||
|
||||
// Convert UTC bought time to local offset time
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(systemConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
ptFirstBoughtDate = ptFirstBoughtDate.ToOffset(offsetTimeSpan);
|
||||
|
||||
dcaLogData.FirstBoughtDate = ptFirstBoughtDate.DateTime;
|
||||
} else {
|
||||
dcaLogData.FirstBoughtDate = Constants.confMinDate;
|
||||
}
|
||||
|
||||
_dcaLog.Add(dcaLogData);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildBuyLogData(List<buyLogData> rawBuyLogData, PTMagicConfiguration systemConfiguration) {
|
||||
foreach (buyLogData rbld in rawBuyLogData) {
|
||||
BuyLogData buyLogData = new BuyLogData();
|
||||
buyLogData.Market = rbld.market;
|
||||
buyLogData.ProfitPercent = rbld.profit;
|
||||
buyLogData.TriggerValue = rbld.triggerValue;
|
||||
buyLogData.CurrentValue = rbld.currentValue;
|
||||
buyLogData.CurrentPrice = rbld.currentPrice;
|
||||
buyLogData.PercChange = rbld.percChange;
|
||||
buyLogData.BuyStrategy = rbld.buyStrategy;
|
||||
buyLogData.CurrentLowBBValue = rbld.BBLow;
|
||||
buyLogData.CurrentHighBBValue = rbld.BBHigh;
|
||||
buyLogData.BBTrigger = rbld.BBTrigger;
|
||||
|
||||
if (buyLogData.BuyStrategy == null) buyLogData.BuyStrategy = "";
|
||||
|
||||
if (rbld.positive != null) {
|
||||
buyLogData.IsTrailing = rbld.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
} else {
|
||||
if (rbld.buyStrategies != null) {
|
||||
foreach (PTStrategy bs in rbld.buyStrategies) {
|
||||
Strategy buyStrategy = new Strategy();
|
||||
buyStrategy.Type = bs.type;
|
||||
buyStrategy.Name = bs.name;
|
||||
buyStrategy.EntryValue = bs.entryValue;
|
||||
buyStrategy.EntryValueLimit = bs.entryValueLimit;
|
||||
buyStrategy.TriggerValue = bs.triggerValue;
|
||||
buyStrategy.CurrentValue = bs.currentValue;
|
||||
buyStrategy.CurrentValuePercentage = bs.currentValuePercentage;
|
||||
buyStrategy.Decimals = bs.decimals;
|
||||
buyStrategy.IsTrailing = bs.positive.IndexOf("true", StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
|
||||
buyLogData.BuyStrategies.Add(buyStrategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_buyLog.Add(buyLogData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.Helper;
|
||||
|
||||
namespace Core.Main.DataObjects {
|
||||
|
||||
public class TransactionData {
|
||||
private List<Transaction> _transactions = new List<Transaction>();
|
||||
|
||||
public TransactionData(string basePath) {
|
||||
string transactionsFilePath = basePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "Transactions.json";
|
||||
if (File.Exists(transactionsFilePath)) {
|
||||
this._transactions = JsonConvert.DeserializeObject<List<Transaction>>(File.ReadAllText(transactionsFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Transaction> Transactions {
|
||||
get {
|
||||
return _transactions;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveTransactions(string basePath) {
|
||||
FileHelper.WriteTextToFile(basePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar, "Transactions.json", JsonConvert.SerializeObject(this.Transactions));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Helper {
|
||||
public static class CertificateHelper {
|
||||
|
||||
public static bool AllwaysGoodCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration;
|
||||
|
||||
namespace Core.Helper {
|
||||
public class EncryptionHelper {
|
||||
|
||||
#region Properties
|
||||
|
||||
public static string CryptoMainSaltValue {
|
||||
get {
|
||||
return "b3+Pz.~L<R 8NH-p=Ze<smbpb*]dP,%d9d{P{DC)R$xf]s|6UC-d)X[y_kDR^EsL";
|
||||
}
|
||||
}
|
||||
|
||||
public static string CryptoSaltValue {
|
||||
get {
|
||||
return "/-T:_~Z|j~0%@~|?7,L~]:us9-=VO[.0V[nZDYTjnUeHcka#hdQ{U^YHv:0sJlfk";
|
||||
}
|
||||
}
|
||||
public static string CryptoInitVector {
|
||||
get {
|
||||
return "qWEE:ADg)}6b;V{B";
|
||||
}
|
||||
}
|
||||
public static string CryptoPassPhrase {
|
||||
get {
|
||||
return "KUBD`o.]*#CCL n9m}tZN4B4~>2EK>((/xnTbWdTo:/5_$hq8ja8yOq% j}M6zTM";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methoden
|
||||
|
||||
#region Passwortverschlüsselung
|
||||
public static string CreateHash(string password, string randomSalt) {
|
||||
// Generate a random salt
|
||||
byte[] salt = Encoding.UTF8.GetBytes(EncryptionHelper.CryptoMainSaltValue + randomSalt);
|
||||
byte[] hash = PBKDF2(password, salt, 64000, 24);
|
||||
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
public static bool SlowEquals(string aHash, string bHash) {
|
||||
byte[] a = Encoding.UTF8.GetBytes(aHash);
|
||||
byte[] b = Encoding.UTF8.GetBytes(bHash);
|
||||
|
||||
uint diff = (uint)a.Length ^ (uint)b.Length;
|
||||
for (int i = 0; i < a.Length && i < b.Length; i++) {
|
||||
diff |= (uint)(a[i] ^ b[i]);
|
||||
}
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes) {
|
||||
using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt)) {
|
||||
pbkdf2.IterationCount = iterations;
|
||||
return pbkdf2.GetBytes(outputBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Standardverschlüsselung
|
||||
public static string Encrypt(string plainText) {
|
||||
return Encrypt(plainText, EncryptionHelper.CryptoPassPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256);
|
||||
}
|
||||
|
||||
public static string Decrypt(string cipherText) {
|
||||
return Decrypt(cipherText, EncryptionHelper.CryptoPassPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256, true);
|
||||
}
|
||||
|
||||
public static string Encrypt(string plainText, string passPhrase) {
|
||||
return Encrypt(plainText, passPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256);
|
||||
}
|
||||
|
||||
public static string Decrypt(string cipherText, string passPhrase) {
|
||||
return Decrypt(cipherText, passPhrase, EncryptionHelper.CryptoSaltValue, "SHA512", 2, EncryptionHelper.CryptoInitVector, 256, true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts specified plaintext using Rijndael symmetric key algorithm
|
||||
/// and returns a base64-encoded result.
|
||||
/// </summary>
|
||||
/// <param name="plainText">
|
||||
/// Plaintext value to be encrypted.
|
||||
/// </param>
|
||||
/// <param name="passPhrase">
|
||||
/// Passphrase from which a pseudo-random password will be derived. The
|
||||
/// derived password will be used to generate the encryption key.
|
||||
/// Passphrase can be any string. In this example we assume that this
|
||||
/// passphrase is an ASCII string.
|
||||
/// </param>
|
||||
/// <param name="saltValue">
|
||||
/// Salt value used along with passphrase to generate password. Salt can
|
||||
/// be any string. In this example we assume that salt is an ASCII string.
|
||||
/// </param>
|
||||
/// <param name="hashAlgorithm">
|
||||
/// Hash algorithm used to generate password. Allowed values are: "MD5" and
|
||||
/// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
|
||||
/// </param>
|
||||
/// <param name="passwordIterations">
|
||||
/// Number of iterations used to generate password. One or two iterations
|
||||
/// should be enough.
|
||||
/// </param>
|
||||
/// <param name="initVector">
|
||||
/// Initialization vector (or IV). This value is required to encrypt the
|
||||
/// first block of plaintext data. For RijndaelManaged class IV must be
|
||||
/// exactly 16 ASCII characters long.
|
||||
/// </param>
|
||||
/// <param name="keySize">
|
||||
/// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
|
||||
/// Longer keys are more secure than shorter keys.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Encrypted value formatted as a base64-encoded string.
|
||||
/// </returns>
|
||||
public static string Encrypt(string plainText,
|
||||
string passPhrase,
|
||||
string saltValue,
|
||||
string hashAlgorithm,
|
||||
int passwordIterations,
|
||||
string initVector,
|
||||
int keySize) {
|
||||
// Convert strings into byte arrays.
|
||||
byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
|
||||
byte[] saltValueBytes = Encoding.UTF8.GetBytes(saltValue);
|
||||
|
||||
// Convert our plaintext into a byte array.
|
||||
// Let us assume that plaintext contains UTF8-encoded characters.
|
||||
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
|
||||
// First, we must create a password, from which the key will be derived.
|
||||
// This password will be generated from the specified passphrase and
|
||||
// salt value. The password will be created using the specified hash
|
||||
// algorithm. Password creation can be done in several iterations.
|
||||
PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
|
||||
|
||||
// Use the password to generate pseudo-random bytes for the encryption
|
||||
// key. Specify the size of the key in bytes (instead of bits).
|
||||
byte[] keyBytes = password.GetBytes(keySize / 8);
|
||||
|
||||
// Create uninitialized Rijndael encryption object.
|
||||
RijndaelManaged symmetricKey = new RijndaelManaged();
|
||||
|
||||
// It is reasonable to set encryption mode to Cipher Block Chaining
|
||||
// (CBC). Use default options for other symmetric key parameters.
|
||||
symmetricKey.Mode = CipherMode.CBC;
|
||||
|
||||
// Generate encryptor from the existing key bytes and initialization
|
||||
// vector. Key size will be defined based on the number of the key
|
||||
// bytes.
|
||||
ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
|
||||
|
||||
// Define memory stream which will be used to hold encrypted data.
|
||||
MemoryStream memoryStream = new MemoryStream();
|
||||
|
||||
// Define cryptographic stream (always use Write mode for encryption).
|
||||
CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
|
||||
// Start encrypting.
|
||||
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
|
||||
|
||||
// Finish encrypting.
|
||||
cryptoStream.FlushFinalBlock();
|
||||
|
||||
// Convert our encrypted data from a memory stream into a byte array.
|
||||
byte[] cipherTextBytes = memoryStream.ToArray();
|
||||
|
||||
// Close both streams.
|
||||
memoryStream.Close();
|
||||
cryptoStream.Close();
|
||||
|
||||
// Convert encrypted data into a base64-encoded string.
|
||||
string cipherText = Convert.ToBase64String(cipherTextBytes);
|
||||
|
||||
// Return encrypted string.
|
||||
return cipherText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts specified ciphertext using Rijndael symmetric key algorithm.
|
||||
/// </summary>
|
||||
/// <param name="cipherText">
|
||||
/// Base64-formatted ciphertext value.
|
||||
/// </param>
|
||||
/// <param name="passPhrase">
|
||||
/// Passphrase from which a pseudo-random password will be derived. The
|
||||
/// derived password will be used to generate the encryption key.
|
||||
/// Passphrase can be any string. In this example we assume that this
|
||||
/// passphrase is an ASCII string.
|
||||
/// </param>
|
||||
/// <param name="saltValue">
|
||||
/// Salt value used along with passphrase to generate password. Salt can
|
||||
/// be any string. In this example we assume that salt is an ASCII string.
|
||||
/// </param>
|
||||
/// <param name="hashAlgorithm">
|
||||
/// Hash algorithm used to generate password. Allowed values are: "MD5" and
|
||||
/// "SHA1". SHA1 hashes are a bit slower, but more secure than MD5 hashes.
|
||||
/// </param>
|
||||
/// <param name="passwordIterations">
|
||||
/// Number of iterations used to generate password. One or two iterations
|
||||
/// should be enough.
|
||||
/// </param>
|
||||
/// <param name="initVector">
|
||||
/// Initialization vector (or IV). This value is required to encrypt the
|
||||
/// first block of plaintext data. For RijndaelManaged class IV must be
|
||||
/// exactly 16 ASCII characters long.
|
||||
/// </param>
|
||||
/// <param name="keySize">
|
||||
/// Size of encryption key in bits. Allowed values are: 128, 192, and 256.
|
||||
/// Longer keys are more secure than shorter keys.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Decrypted string value.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Most of the logic in this function is similar to the Encrypt
|
||||
/// logic. In order for decryption to work, all parameters of this function
|
||||
/// - except cipherText value - must match the corresponding parameters of
|
||||
/// the Encrypt function which was called to generate the
|
||||
/// ciphertext.
|
||||
/// </remarks>
|
||||
public static string Decrypt(string cipherText,
|
||||
string passPhrase,
|
||||
string saltValue,
|
||||
string hashAlgorithm,
|
||||
int passwordIterations,
|
||||
string initVector,
|
||||
int keySize,
|
||||
bool doDecrypt) {
|
||||
if (doDecrypt) {
|
||||
// Convert strings defining encryption key characteristics into byte
|
||||
// arrays.
|
||||
byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
|
||||
byte[] saltValueBytes = Encoding.UTF8.GetBytes(saltValue);
|
||||
|
||||
// Convert our ciphertext into a byte array.
|
||||
byte[] cipherTextBytes = Convert.FromBase64String(cipherText);
|
||||
|
||||
// First, we must create a password, from which the key will be
|
||||
// derived. This password will be generated from the specified
|
||||
// passphrase and salt value. The password will be created using
|
||||
// the specified hash algorithm. Password creation can be done in
|
||||
// several iterations.
|
||||
PasswordDeriveBytes password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
|
||||
|
||||
// Use the password to generate pseudo-random bytes for the encryption
|
||||
// key. Specify the size of the key in bytes (instead of bits).
|
||||
byte[] keyBytes = password.GetBytes(keySize / 8);
|
||||
|
||||
// Create uninitialized Rijndael encryption object.
|
||||
RijndaelManaged symmetricKey = new RijndaelManaged();
|
||||
|
||||
// It is reasonable to set encryption mode to Cipher Block Chaining
|
||||
// (CBC). Use default options for other symmetric key parameters.
|
||||
symmetricKey.Mode = CipherMode.CBC;
|
||||
|
||||
// Generate decryptor from the existing key bytes and initialization
|
||||
// vector. Key size will be defined based on the number of the key
|
||||
// bytes.
|
||||
ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
|
||||
|
||||
// Define memory stream which will be used to hold encrypted data.
|
||||
MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
|
||||
|
||||
// Define cryptographic stream (always use Read mode for encryption).
|
||||
CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
|
||||
|
||||
// Since at this point we don't know what the size of decrypted data
|
||||
// will be, allocate the buffer long enough to hold ciphertext;
|
||||
// plaintext is never longer than ciphertext.
|
||||
byte[] plainTextBytes = new byte[cipherTextBytes.Length];
|
||||
|
||||
// Start decrypting.
|
||||
int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
|
||||
|
||||
// Close both streams.
|
||||
memoryStream.Close();
|
||||
cryptoStream.Close();
|
||||
|
||||
// Convert decrypted data into a string.
|
||||
// Let us assume that the original plaintext string was UTF8-encoded.
|
||||
string plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
|
||||
|
||||
// Return decrypted string.
|
||||
return plainText;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Core.Main;
|
||||
|
||||
namespace Core.Helper {
|
||||
public static class FileHelper {
|
||||
public static void WriteTextToFile(string folderPath, string fileName, string text) {
|
||||
FileHelper.WriteTextToFile(folderPath, fileName, text, Constants.confMinDate, Constants.confMinDate);
|
||||
}
|
||||
|
||||
public static void WriteTextToFile(string folderPath, string fileName, string text, DateTime creationTime, DateTime lastWriteTime) {
|
||||
if (!Directory.Exists(folderPath)) {
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
File.WriteAllText(folderPath + fileName, text);
|
||||
|
||||
if (creationTime != Constants.confMinDate) {
|
||||
File.SetCreationTimeUtc(folderPath + fileName, creationTime);
|
||||
}
|
||||
|
||||
if (lastWriteTime != Constants.confMinDate) {
|
||||
File.SetLastWriteTimeUtc(folderPath + fileName, lastWriteTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateBackup(string filePath, string backupFolder) {
|
||||
FileHelper.CreateBackup(filePath, backupFolder, "");
|
||||
}
|
||||
|
||||
public static void CreateBackup(string filePath, string backupFolder, string backupFileName) {
|
||||
if (!Directory.Exists(backupFolder)) {
|
||||
Directory.CreateDirectory(backupFolder);
|
||||
}
|
||||
|
||||
FileInfo file = new FileInfo(filePath);
|
||||
|
||||
string backupFilePath = backupFolder + DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss") + "_" + file.Name;
|
||||
if (!backupFileName.Equals("")) {
|
||||
backupFilePath = backupFolder + backupFileName;
|
||||
}
|
||||
|
||||
File.Copy(file.FullName, backupFilePath, true);
|
||||
}
|
||||
|
||||
public static void CleanupFilesMinutes(string folderPath, int maxMinutes) {
|
||||
if (!Directory.Exists(folderPath)) {
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
DirectoryInfo folder = new DirectoryInfo(folderPath);
|
||||
foreach (FileInfo file in folder.GetFiles()) {
|
||||
DateTime maxAge = DateTime.Now.AddMinutes(-maxMinutes);
|
||||
|
||||
if (file.LastWriteTime < maxAge) {
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanupFiles(string folderPath, int maxHours) {
|
||||
if (!Directory.Exists(folderPath)) {
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
DirectoryInfo folder = new DirectoryInfo(folderPath);
|
||||
foreach (FileInfo file in folder.GetFiles()) {
|
||||
DateTime maxAge = DateTime.Now.AddHours(-(maxHours + 1));
|
||||
|
||||
if (file.LastWriteTime < maxAge) {
|
||||
File.Delete(file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog.Extensions.Logging;
|
||||
|
||||
namespace Core.Helper {
|
||||
public class LogHelper {
|
||||
private readonly ILogger<LogHelper> log;
|
||||
|
||||
public LogHelper(ILogger<LogHelper> logger) {
|
||||
log = logger;
|
||||
}
|
||||
|
||||
public void DoLogInfo(string message) {
|
||||
if (log.IsEnabled(LogLevel.Information)) log.LogInformation(message);
|
||||
}
|
||||
|
||||
public void DoLogWarn(string message) {
|
||||
if (log.IsEnabled(LogLevel.Warning)) log.LogWarning(message);
|
||||
}
|
||||
|
||||
public void DoLogError(string message) {
|
||||
if (log.IsEnabled(LogLevel.Error)) log.LogError(message);
|
||||
}
|
||||
|
||||
public void DoLogCritical(string message, System.Exception ex) {
|
||||
if (log.IsEnabled(LogLevel.Critical)) log.LogCritical(ex, message);
|
||||
}
|
||||
|
||||
public void DoLogDebug(string message) {
|
||||
if (log.IsEnabled(LogLevel.Debug)) log.LogDebug(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog.Extensions.Logging;
|
||||
|
||||
namespace Core.Helper {
|
||||
public static class ServiceHelper {
|
||||
|
||||
public static IServiceProvider BuildLoggerService() {
|
||||
return ServiceHelper.BuildLoggerService("");
|
||||
}
|
||||
|
||||
public static IServiceProvider BuildLoggerService(string basePath) {
|
||||
ServiceCollection services = new ServiceCollection();
|
||||
|
||||
services.AddTransient<LogHelper>();
|
||||
|
||||
services.AddSingleton<ILoggerFactory, LoggerFactory>();
|
||||
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
|
||||
services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
|
||||
|
||||
ServiceProvider serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
|
||||
//configure NLog
|
||||
loggerFactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });
|
||||
loggerFactory.ConfigureNLog(basePath + "nlog.config");
|
||||
|
||||
return serviceProvider;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,549 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Globalization;
|
||||
using Core.Main;
|
||||
|
||||
namespace Core.Helper {
|
||||
|
||||
public class SystemHelper {
|
||||
private static bool AllwaysGoodCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks, if a string is numeric.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to check.</param>
|
||||
/// <returns>True, if the string is numeric.</returns>
|
||||
public static bool IsNumeric(string s) {
|
||||
try {
|
||||
Int32.Parse(s);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsInteger(double d) {
|
||||
return d % 1 == 0;
|
||||
}
|
||||
|
||||
public static bool IsBoolean(string s) {
|
||||
try {
|
||||
Boolean.Parse(s);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks, if a string is a double value.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to check.</param>
|
||||
/// <returns>True, if the string is a double value.</returns>
|
||||
public static bool IsDouble(string s) {
|
||||
try {
|
||||
Double.Parse(s);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsDouble(string s, string culture) {
|
||||
try {
|
||||
Double.Parse(s, new CultureInfo(culture));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks, if a string is a DateTime value.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to check.</param>
|
||||
/// <returns>True, if the string is a DateTime value.</returns>
|
||||
public static bool IsDateTime(string s) {
|
||||
try {
|
||||
DateTime.Parse(s);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Konvertiert einen Text zu einem Integer-Wert mit Fehlerbehandlung.
|
||||
/// </summary>
|
||||
/// <param name="text">Zu konvertierender Text.</param>
|
||||
/// <param name="defaultValue">Der Vorgabewert für den Fall, dass keine gültige Zahl eingegeben wurde.</param>
|
||||
/// <returns>Den Text als Integer. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.</returns>
|
||||
public static int TextToInteger(string text, int defaultValue) {
|
||||
int result = defaultValue;
|
||||
try {
|
||||
string localText = text.Replace(".", "");
|
||||
result = Convert.ToInt32(localText.Trim());
|
||||
} catch { }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Konvertiert einen Text zu einem Integer64-Wert mit Fehlerbehandlung.
|
||||
/// </summary>
|
||||
/// <param name="text">Zu konvertierender Text.</param>
|
||||
/// <param name="defaultValue">Der Vorgabewert für den Fall, dass keine gültige Zahl eingegeben wurde.</param>
|
||||
/// <returns>Den Text als Integer64. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.</returns>
|
||||
public static Int64 TextToInteger64(string text, Int64 defaultValue) {
|
||||
Int64 result = defaultValue;
|
||||
try {
|
||||
string localText = text.Replace(".", "");
|
||||
result = Convert.ToInt64(localText.Trim());
|
||||
} catch { }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double TextToDouble(string text, double defaultValue, string culture) {
|
||||
double result = defaultValue;
|
||||
try {
|
||||
if (!string.IsNullOrEmpty(text)) {
|
||||
double.TryParse(text, NumberStyles.Any, new System.Globalization.CultureInfo(culture), out result);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Konvertiert einen Text zu einem DateTime-Wert mit Fehlerbehandlung.
|
||||
/// </summary>
|
||||
/// <param name="text">Zu konvertierender Text.</param>
|
||||
/// <param name="defaultValue">Der Vorgabewert für den Fall, dass keine gültige DateTime eingegeben wurde.</param>
|
||||
/// <returns>Den Text als DateTime. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.</returns>
|
||||
public static DateTime TextToDateTime(string text, DateTime defaultValue) {
|
||||
DateTime result = defaultValue;
|
||||
try {
|
||||
result = Convert.ToDateTime(text.Trim());
|
||||
} catch { }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static DateTime TextToDateTime(string text, DateTime defaultValue, string culture) {
|
||||
DateTime result = defaultValue;
|
||||
try {
|
||||
result = Convert.ToDateTime(text.Trim(), new System.Globalization.CultureInfo(culture));
|
||||
} catch { }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Konvertiert einen Text zu einem Boolean-Wert mit Fehlerbehandlung.
|
||||
/// </summary>
|
||||
/// <param name="text">Zu konvertierender Text.</param>
|
||||
/// <param name="defaultValue">Der Vorgabewert für den Fall, dass keine gültige Boolean eingegeben wurde.</param>
|
||||
/// <returns>Den Text als Boolean. Wenn die Konvertierung fehlschlägt, dann wird der Defaultwert zurückgegeben.</returns>
|
||||
public static bool TextToBoolean(string text, bool defaultValue) {
|
||||
bool result = defaultValue;
|
||||
try {
|
||||
result = Convert.ToBoolean(text.Trim());
|
||||
} catch {
|
||||
try {
|
||||
int intValue = Convert.ToInt32(text.Trim());
|
||||
result = intValue == 0 ? false : true;
|
||||
} catch { }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string SplitCamelCase(string s) {
|
||||
string result = "";
|
||||
|
||||
string whiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÜÖßabcdefghijklmnopqrstuvwxyzäüö0123456789_- ";
|
||||
|
||||
if (!string.IsNullOrEmpty(s)) {
|
||||
for (int i = 0; i < s.Length; i++) {
|
||||
if (char.IsUpper(s[i]) || char.IsNumber(s[i])) {
|
||||
if (i > 0 && whiteList.Contains(s[i - 1].ToString())) {
|
||||
if (char.IsUpper(s[i])) {
|
||||
if (!char.IsUpper(s[i - 1]) && !char.IsNumber(s[i - 1])) result += " ";
|
||||
} else if (char.IsNumber(s[i])) {
|
||||
if (!char.IsNumber(s[i - 1])) result += " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
result += s[i].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a string using a whitelist.
|
||||
/// </summary>
|
||||
/// <param name="text">Text to clear.</param>
|
||||
/// <param name="allowedCharacters">Allowed characters.</param>
|
||||
/// <returns>The cleared text.</returns>
|
||||
public static string StripBadCode(string text, string allowedCharacters) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (text != null) {
|
||||
for (int i = 0; i < text.Length; i++) {
|
||||
if (allowedCharacters.Contains(text[i].ToString())) sb.Append(text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static bool CheckForBadCode(string text, string allowedCharacters) {
|
||||
bool result = false;
|
||||
for (int i = 0; i < text.Length; i++) {
|
||||
if (!allowedCharacters.Contains(text[i].ToString())) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schneidet einen Text nach x Zeichen ab
|
||||
/// </summary>
|
||||
/// <param name="text">Der Text, der gekürzt werden soll.</param>
|
||||
/// <param name="maxLength">Die maximale Länge, auf die der Text gekürzt werden soll.</param>
|
||||
/// <returns>Der gekürzte Text.</returns>
|
||||
public static string CutText(string text, int maxLength, bool addDots) {
|
||||
string result = text;
|
||||
|
||||
if (result.Length > maxLength) {
|
||||
result = result.Substring(0, maxLength);
|
||||
|
||||
if (addDots) result += "...";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt den Teilstring eines Zeitstring, der die Stunden darstellt.
|
||||
/// </summary>
|
||||
public static string GetHourFromString(string timeString) {
|
||||
string result = "";
|
||||
|
||||
if (timeString.Contains(":")) {
|
||||
string[] arrTime = timeString.Split(":".ToCharArray());
|
||||
result = arrTime[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermittelt den Teilstring eines Zeitstring, der die Minuten darstellt.
|
||||
/// </summary>
|
||||
public static string GetMinutesFromString(string timeString) {
|
||||
string result = "";
|
||||
|
||||
if (timeString.Contains(":")) {
|
||||
string[] arrTime = timeString.Split(":".ToCharArray());
|
||||
result = arrTime[1];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> ConvertTokenStringToList(string tokenizedString, string separator) {
|
||||
List<string> result = new List<string>();
|
||||
|
||||
if (!String.IsNullOrEmpty(tokenizedString) && !String.IsNullOrEmpty(separator)) {
|
||||
string[] arrTokens = tokenizedString.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < arrTokens.Length; i++) {
|
||||
result.Add(arrTokens[i].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<int> ConvertTokenStringToListInt(string tokenizedString, string separator) {
|
||||
List<int> result = new List<int>();
|
||||
|
||||
if (!String.IsNullOrEmpty(tokenizedString) && !String.IsNullOrEmpty(separator)) {
|
||||
string[] arrTokens = tokenizedString.Split(separator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < arrTokens.Length; i++) {
|
||||
result.Add(Convert.ToInt32(arrTokens[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ConvertListToTokenString(List<string> tokenList, string separator, bool cropDoubleSeparators) {
|
||||
string result = "";
|
||||
|
||||
if (tokenList.Count > 0) {
|
||||
for (int i = 0; i < tokenList.Count; i++) {
|
||||
result += tokenList[i].Trim() + separator;
|
||||
}
|
||||
|
||||
if (cropDoubleSeparators)result = result.Replace(separator + separator, "");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ConvertListToTokenString(List<int> tokenList, string separator) {
|
||||
string result = "";
|
||||
|
||||
if (tokenList.Count > 0) {
|
||||
for (int i = 0; i < tokenList.Count; i++) {
|
||||
result += tokenList[i].ToString() + separator;
|
||||
}
|
||||
|
||||
result += separator;
|
||||
result = result.Replace(separator + separator, "");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<object> ConvertToObjectList<T>(List<T> inputList) {
|
||||
List<object> result = new List<object>();
|
||||
|
||||
foreach (T item in inputList) {
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Hashtable ConvertTokenStringToHashtable(string tokenizedString, string pairSeparator, string fieldSeperator) {
|
||||
Hashtable result = new Hashtable();
|
||||
|
||||
if (!String.IsNullOrEmpty(tokenizedString) && !String.IsNullOrEmpty(pairSeparator) && !String.IsNullOrEmpty(fieldSeperator)) {
|
||||
string[] arrTokens = tokenizedString.Split(pairSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < arrTokens.Length; i++) {
|
||||
string[] arrKeyValuePair = arrTokens[i].Split(fieldSeperator.ToCharArray());
|
||||
|
||||
result.Add(arrKeyValuePair[0], arrKeyValuePair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string ConvertHashtableToTokenString(Hashtable tokenHashtable, string pairSeparator, string fieldSeperator) {
|
||||
string result = "";
|
||||
|
||||
if (tokenHashtable.Keys.Count > 0) {
|
||||
foreach (string key in tokenHashtable.Keys) {
|
||||
result += key + fieldSeperator + tokenHashtable[key] + pairSeparator;
|
||||
}
|
||||
|
||||
result += pairSeparator;
|
||||
result = result.Replace(pairSeparator + pairSeparator, "");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetProperDurationTime(int durationSeconds, bool includeDays = true) {
|
||||
string result = "";
|
||||
|
||||
int days = (int)Math.Floor((double)durationSeconds / (60.0 * 60.0 * 24.0));
|
||||
if (!includeDays) days = 0;
|
||||
|
||||
int hours = (int)Math.Floor((double)durationSeconds / (60.0 * 60.0)) - days * 24;
|
||||
int minutes = (int)Math.Floor((double)durationSeconds / 60.0) - (hours * 60) - (days * 24 * 60);
|
||||
int seconds = durationSeconds - (minutes * 60) - (hours * 60 * 60) - (days * 24 * 60 * 60);
|
||||
|
||||
if (days > 0) {
|
||||
result += days.ToString() + "d";
|
||||
}
|
||||
|
||||
if (hours > 0) {
|
||||
if (days > 0) result += " ";
|
||||
result += hours.ToString() + "h";
|
||||
}
|
||||
|
||||
if (minutes > 0) {
|
||||
if (hours > 0 || days > 0) result += " ";
|
||||
result += minutes.ToString() + "m";
|
||||
}
|
||||
|
||||
if (seconds > 0) {
|
||||
if (minutes > 0 || hours > 0 || days > 0) result += " ";
|
||||
result += seconds.ToString() + "s";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void AddValueToStringBuilder(StringBuilder sb, string value, int length, bool fillField, string delimiter) {
|
||||
if (!string.IsNullOrEmpty(value)) {
|
||||
if (value.Length > length)
|
||||
sb.Append(value.Substring(0, length)); // Beschneiden
|
||||
else {
|
||||
if (fillField)
|
||||
sb.Append(value.PadRight(length));
|
||||
else
|
||||
sb.Append(value);
|
||||
}
|
||||
} else {
|
||||
if (fillField)
|
||||
sb.Append(string.Empty.PadRight(length));
|
||||
}
|
||||
sb.Append(delimiter);
|
||||
}
|
||||
|
||||
public static bool UrlIsReachable(string url) {
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.DefaultConnectionLimit = 9999;
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AllwaysGoodCertificate);
|
||||
|
||||
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
|
||||
request.Timeout = 10000;
|
||||
request.Method = "GET";
|
||||
|
||||
try {
|
||||
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) {
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
}
|
||||
} catch (WebException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetMarketLink(string platform, string exchange, string market, string mainMarket) {
|
||||
string result = "#";
|
||||
if (platform.Equals("TradingView")) {
|
||||
result = "https://www.tradingview.com/chart/?symbol=" + exchange.ToUpper() + ":";
|
||||
|
||||
string pairName = SystemHelper.StripBadCode(market, Constants.WhiteListMinimal);
|
||||
|
||||
if (pairName.StartsWith(mainMarket)) {
|
||||
pairName = pairName.Replace(mainMarket, "") + mainMarket;
|
||||
}
|
||||
|
||||
result += pairName;
|
||||
} else {
|
||||
switch (exchange) {
|
||||
case "Bittrex":
|
||||
result = "https://bittrex.com/Market/Index?MarketName=" + market;
|
||||
break;
|
||||
case "Binance":
|
||||
result = "https://www.binance.com/trade.html?symbol=" + market;
|
||||
break;
|
||||
case "Poloniex":
|
||||
result = "https://poloniex.com/exchange#" + market.ToLower();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetFullMarketName(string mainMarket, string market, string exchange) {
|
||||
string result = market;
|
||||
|
||||
switch (exchange) {
|
||||
case "Bittrex":
|
||||
result = mainMarket + "-" + market;
|
||||
break;
|
||||
case "Binance":
|
||||
result = market + mainMarket;
|
||||
break;
|
||||
case "Poloniex":
|
||||
result = mainMarket + "_" + market;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetTradingViewSymbol(string exchange, string market, string mainMarket) {
|
||||
string result = exchange.ToUpper() + ":";
|
||||
|
||||
string pairName = SystemHelper.StripBadCode(market, Constants.WhiteListMinimal);
|
||||
|
||||
if (pairName.StartsWith(mainMarket)) {
|
||||
pairName = pairName.Replace(mainMarket, "") + mainMarket;
|
||||
}
|
||||
|
||||
result += pairName;
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetCurrencySymbol(string code) {
|
||||
string result = code;
|
||||
try {
|
||||
System.Globalization.RegionInfo regionInfo = (from culture in System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.AllCultures)
|
||||
where culture.Name.Length > 0 && !culture.IsNeutralCulture
|
||||
let region = new System.Globalization.RegionInfo(culture.LCID)
|
||||
where String.Equals(region.ISOCurrencySymbol, code, StringComparison.InvariantCultureIgnoreCase)
|
||||
select region).First();
|
||||
result = regionInfo.CurrencySymbol;
|
||||
} catch {
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string PropertyToString(object property) {
|
||||
string result = property.ToString();
|
||||
|
||||
if (!property.ToString().Equals("true", StringComparison.InvariantCultureIgnoreCase) && !property.ToString().Equals("false", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
try {
|
||||
double resultDouble = Convert.ToDouble(property);
|
||||
result = resultDouble.ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
result = property.ToString().ToLower();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsRecentVersion(string currentVersion, string latestVersion) {
|
||||
bool result = true;
|
||||
|
||||
List<int> currentVersionInfo = SystemHelper.ConvertTokenStringToListInt(currentVersion, ".");
|
||||
List<int> latestVersionInfo = SystemHelper.ConvertTokenStringToListInt(latestVersion, ".");
|
||||
|
||||
if (currentVersionInfo[0] < latestVersionInfo[0]) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (currentVersionInfo[0] == latestVersionInfo[0] && currentVersionInfo[1] < latestVersionInfo[1]) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (currentVersionInfo[0] == latestVersionInfo[0] && currentVersionInfo[1] == latestVersionInfo[1] && currentVersionInfo[2] < latestVersionInfo[2]) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Core.Helper {
|
||||
public static class TelegramHelper {
|
||||
public static void SendMessage(string botToken, Int64 chatId, string message, bool useSilentMode, LogHelper log) {
|
||||
if (!botToken.Equals("") && chatId != 0) {
|
||||
try {
|
||||
TelegramBotClient botClient = new TelegramBotClient(botToken);
|
||||
System.Threading.Tasks.Task<Message> sentMessage = botClient.SendTextMessageAsync(chatId, message, ParseMode.Markdown, false, useSilentMode);
|
||||
|
||||
if (sentMessage.IsCompleted) {
|
||||
log.DoLogDebug("Telegram message sent to ChatId " + chatId.ToString() + " on Bot Token '" + botToken + "'");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Exception sending telegram message to ChatId " + chatId.ToString() + " on Bot Token '" + botToken + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
|
||||
namespace Core.Helper {
|
||||
|
||||
public class ZIPHelper {
|
||||
|
||||
public static bool CreateZipFile(ArrayList filePaths, string outputPath) {
|
||||
bool result = true;
|
||||
|
||||
ZipOutputStream pack = new ZipOutputStream(File.Create(outputPath));
|
||||
try {
|
||||
|
||||
// set compression level
|
||||
pack.SetLevel(5);
|
||||
|
||||
foreach (string filePath in filePaths) {
|
||||
FileStream fs = File.OpenRead(filePath);
|
||||
|
||||
// allocate buffer
|
||||
byte[] buffer = new byte[fs.Length];
|
||||
fs.Read(buffer, 0, buffer.Length);
|
||||
|
||||
// write the zip entry and its data
|
||||
ZipEntry entry = new ZipEntry(filePath.Substring(filePath.LastIndexOf(Path.DirectorySeparatorChar) + 1));
|
||||
pack.PutNextEntry(entry);
|
||||
pack.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
} catch {
|
||||
result = false;
|
||||
} finally {
|
||||
pack.Finish();
|
||||
pack.Close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ArrayList ExtractFileFromZipFile(string filePath, string destinationPath, bool isInvoicePackage) {
|
||||
ArrayList result = new ArrayList();
|
||||
|
||||
ZipFile zip = new ZipFile(File.OpenRead(filePath));
|
||||
try {
|
||||
foreach (ZipEntry entry in zip) {
|
||||
if (entry.IsFile) {
|
||||
string fileName = entry.Name;
|
||||
if (isInvoicePackage) {
|
||||
fileName = fileName.Replace("unsigned", "signed");
|
||||
}
|
||||
|
||||
result.Add(fileName);
|
||||
|
||||
Stream inputStream = zip.GetInputStream(entry);
|
||||
FileStream fileStream = new FileStream(destinationPath + fileName, FileMode.Create);
|
||||
try {
|
||||
CopyStream(inputStream, fileStream);
|
||||
} finally {
|
||||
fileStream.Close();
|
||||
inputStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
zip.Close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void CopyStream(Stream input, Stream output) {
|
||||
byte[] buffer = new byte[0x1000];
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0) {
|
||||
output.Write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Core.Main {
|
||||
public static class Constants {
|
||||
// Minimales Datum (für NULL)
|
||||
public static DateTime confMinDate = (DateTime)System.Data.SqlTypes.SqlDateTime.MinValue;
|
||||
public static DateTime confMaxDate = (DateTime)System.Data.SqlTypes.SqlDateTime.MaxValue;
|
||||
|
||||
public static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public const string WhiteListMinimal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
public const string WhiteListNames = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
|
||||
public const string WhiteListProperties = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.,_ ";
|
||||
|
||||
public const string PTPathTrading = "trading";
|
||||
|
||||
public const string PTMagicPathData = "_data";
|
||||
public const string PTMagicPathPresets = "_presets";
|
||||
public const string PTMagicPathCoinMarketCap = "CoinMarketCap";
|
||||
public const string PTMagicPathExchange = "Exchange";
|
||||
public const string PTMagicPathLogs = "_logs";
|
||||
|
||||
public const int PTMagicBotState_Idle = 0;
|
||||
public const int PTMagicBotState_Running = 1;
|
||||
|
||||
public const double MaxTrendChange = 10000.0;
|
||||
public const double MinTrendChange = -100.0;
|
||||
|
||||
public const double Max24hVolume = Double.MaxValue;
|
||||
|
||||
public const int ValueModeDefault = 0;
|
||||
public const int ValueModeOffset = 1;
|
||||
public const int ValueModeOffsetPercent = 2;
|
||||
|
||||
public const string MarketTrendRelationRelative = "Relative";
|
||||
public const string MarketTrendRelationAbsolute = "Absolute";
|
||||
public const string MarketTrendRelationRelativeTrigger = "RelativeTrigger";
|
||||
|
||||
public static readonly string[] ChartLineColors = new string[] { "#e67e22", "#5d9cec", "#fb6d9d", "#ffffff", "#81c868", "#f05050", "#34d3eb", "#ffbd4a", "#dcdcdc", "#ef1442", "#d73d76", "#9b31c9", "#52e9f1", "#c9b56e", "#b49ec1", "#9885f3", "#85748a", "#85748a", "#85748a", "#b57a4b" };
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Core.Main {
|
||||
|
||||
public class PTMagicConfiguration {
|
||||
private GeneralSettings _generalSettings = null;
|
||||
private AnalyzerSettings _analyzerSettings = null;
|
||||
private SecureSettings _secureSettings = null;
|
||||
|
||||
public PTMagicConfiguration() {
|
||||
LoadSettings(Directory.GetCurrentDirectory());
|
||||
}
|
||||
|
||||
public PTMagicConfiguration(string basePath) {
|
||||
LoadSettings(basePath);
|
||||
}
|
||||
|
||||
private void LoadSettings(string basePath) {
|
||||
if (!basePath.EndsWith(Path.DirectorySeparatorChar)) {
|
||||
basePath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
GeneralSettingsWrapper gsw = JsonConvert.DeserializeObject<GeneralSettingsWrapper>(File.ReadAllText(basePath + "settings.general.json"));
|
||||
_generalSettings = gsw.GeneralSettings;
|
||||
|
||||
AnalyzerSettingsWrapper asw = JsonConvert.DeserializeObject<AnalyzerSettingsWrapper>(File.ReadAllText(basePath + "settings.analyzer.json"));
|
||||
_analyzerSettings = asw.AnalyzerSettings;
|
||||
|
||||
if (!_generalSettings.Application.ProfitTrailerPath.EndsWith(Path.DirectorySeparatorChar)) {
|
||||
_generalSettings.Application.ProfitTrailerPath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
if (!_generalSettings.Application.ProfitTrailerMonitorURL.EndsWith("/")) {
|
||||
_generalSettings.Application.ProfitTrailerMonitorURL += "/";
|
||||
}
|
||||
|
||||
if (File.Exists(basePath + "settings.secure.json")) {
|
||||
SecureSettingsWrapper ssw = JsonConvert.DeserializeObject<SecureSettingsWrapper>(File.ReadAllText(basePath + "settings.secure.json"));
|
||||
_secureSettings = ssw.SecureSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetProfitTrailerLicenseKeyMasked() {
|
||||
string result = "";
|
||||
|
||||
if (!this.GeneralSettings.Application.ProfitTrailerLicense.Equals("")) {
|
||||
result = this.GeneralSettings.Application.ProfitTrailerLicense.Substring(0, 4);
|
||||
|
||||
for (int i = 1; i < this.GeneralSettings.Application.ProfitTrailerLicense.Length - 8; i++) {
|
||||
result += "*";
|
||||
}
|
||||
|
||||
result += this.GeneralSettings.Application.ProfitTrailerLicense.Substring(this.GeneralSettings.Application.ProfitTrailerLicense.Length - 4);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public GeneralSettings GeneralSettings {
|
||||
get {
|
||||
return _generalSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public AnalyzerSettings AnalyzerSettings {
|
||||
get {
|
||||
return _analyzerSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public SecureSettings SecureSettings {
|
||||
get {
|
||||
if (_secureSettings == null) _secureSettings = new SecureSettings();
|
||||
return _secureSettings;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteGeneralSettings(string basePath) {
|
||||
GeneralSettingsWrapper gsWrapper = new GeneralSettingsWrapper();
|
||||
gsWrapper.GeneralSettings = this.GeneralSettings;
|
||||
|
||||
FileHelper.CreateBackup(basePath + "settings.general.json", basePath, "settings.general.json.backup");
|
||||
|
||||
FileHelper.WriteTextToFile(basePath, "settings.general.json", JsonConvert.SerializeObject(gsWrapper, Formatting.Indented));
|
||||
}
|
||||
|
||||
public void WriteAnalyzerSettings(string basePath) {
|
||||
AnalyzerSettingsWrapper asWrapper = new AnalyzerSettingsWrapper();
|
||||
asWrapper.AnalyzerSettings = this.AnalyzerSettings;
|
||||
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings();
|
||||
settings.NullValueHandling = NullValueHandling.Ignore;
|
||||
settings.DefaultValueHandling = DefaultValueHandling.Ignore;
|
||||
|
||||
FileHelper.CreateBackup(basePath + "settings.analyzer.json", basePath, "settings.analyzer.json.backup");
|
||||
|
||||
FileHelper.WriteTextToFile(basePath, "settings.analyzer.json", JsonConvert.SerializeObject(asWrapper, Formatting.Indented, settings));
|
||||
}
|
||||
|
||||
public void WriteSecureSettings(string password, string basePath) {
|
||||
string passwordEncrypted = EncryptionHelper.Encrypt(password);
|
||||
|
||||
this.SecureSettings.MonitorPassword = passwordEncrypted;
|
||||
|
||||
SecureSettingsWrapper ssWrapper = new SecureSettingsWrapper();
|
||||
ssWrapper.SecureSettings = this.SecureSettings;
|
||||
|
||||
FileHelper.WriteTextToFile(basePath, "settings.secure.json", JsonConvert.SerializeObject(ssWrapper, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Core.MarketAnalyzer {
|
||||
public class BaseAnalyzer {
|
||||
public static Dictionary<string, dynamic> GetJsonFromURL(string url, LogHelper log) {
|
||||
Dictionary<string, dynamic> jsonObject = null;
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.ContentType = "application/json";
|
||||
request.UserAgent = "PTMagic.Import";
|
||||
|
||||
try {
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
StreamReader jsonReader = new StreamReader(httpResponse.GetResponseStream());
|
||||
string jsonString = jsonReader.ReadToEnd();
|
||||
jsonReader.Close();
|
||||
|
||||
jsonObject = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(jsonString);
|
||||
|
||||
return jsonObject;
|
||||
} catch (WebException ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static Newtonsoft.Json.Linq.JObject GetSimpleJsonObjectFromURL(string url, LogHelper log, bool onlyLogDebug) {
|
||||
Newtonsoft.Json.Linq.JObject jsonObject = null;
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.ContentType = "application/json";
|
||||
request.UserAgent = "PTMagic.Import";
|
||||
|
||||
try {
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
StreamReader jsonReader = new StreamReader(httpResponse.GetResponseStream());
|
||||
string jsonString = jsonReader.ReadToEnd();
|
||||
jsonReader.Close();
|
||||
|
||||
jsonObject = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JObject>(jsonString);
|
||||
|
||||
return jsonObject;
|
||||
} catch (WebException ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
if (onlyLogDebug) {
|
||||
log.DoLogDebug("Url: " + url + " Message: " + ex.Message);
|
||||
} else {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static List<dynamic> GetSimpleJsonListFromURL(string url, LogHelper log) {
|
||||
List<dynamic> jsonObject = null;
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.ContentType = "application/json";
|
||||
request.UserAgent = "PTMagic.Import";
|
||||
|
||||
try {
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
StreamReader jsonReader = new StreamReader(httpResponse.GetResponseStream());
|
||||
string jsonString = jsonReader.ReadToEnd();
|
||||
jsonReader.Close();
|
||||
|
||||
jsonObject = JsonConvert.DeserializeObject<List<dynamic>>(jsonString);
|
||||
|
||||
return jsonObject;
|
||||
} catch (WebException ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static Newtonsoft.Json.Linq.JArray GetSimpleJsonArrayFromURL(string url, LogHelper log) {
|
||||
Newtonsoft.Json.Linq.JArray jsonObject = null;
|
||||
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.ContentType = "application/json";
|
||||
request.UserAgent = "PTMagic.Import";
|
||||
|
||||
try {
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
StreamReader jsonReader = new StreamReader(httpResponse.GetResponseStream());
|
||||
string jsonString = jsonReader.ReadToEnd();
|
||||
jsonReader.Close();
|
||||
|
||||
jsonObject = JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JArray>(jsonString);
|
||||
|
||||
return jsonObject;
|
||||
} catch (WebException ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static string GetLatestGitHubRelease(LogHelper log, string defaultVersion) {
|
||||
string result = defaultVersion;
|
||||
|
||||
try {
|
||||
string baseUrl = "https://api.github.com/repos/legedric/ptmagic/releases/latest";
|
||||
|
||||
log.DoLogDebug("GitHub - Getting most recent release...");
|
||||
Newtonsoft.Json.Linq.JObject jsonObject = GetSimpleJsonObjectFromURL(baseUrl, log, true);
|
||||
if (jsonObject != null) {
|
||||
log.DoLogDebug("GitHub - Received most recent release.");
|
||||
|
||||
result = jsonObject.GetValue("tag_name").ToString();
|
||||
log.DoLogDebug("GitHub - Most recent release version is " + result);
|
||||
}
|
||||
} catch (WebException ex) {
|
||||
log.DoLogDebug("GitHub version check error: " + ex.Message);
|
||||
} catch (Exception ex) {
|
||||
log.DoLogDebug("GitHub version check error: " + ex.Message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double GetMainFiatCurrencyRate(string currency, LogHelper log) {
|
||||
double result = 1;
|
||||
|
||||
string baseUrl = "http://free.currencyconverterapi.com/api/v5/convert?q=USD_" + currency + "&compact=y";
|
||||
|
||||
log.DoLogDebug("http://free.currencyconverterapi.com - Getting latest exchange rates...");
|
||||
Newtonsoft.Json.Linq.JObject jsonObject = GetSimpleJsonObjectFromURL(baseUrl, log, true);
|
||||
if (jsonObject != null) {
|
||||
log.DoLogDebug("http://free.currencyconverterapi.com - Received latest exchange rates.");
|
||||
|
||||
result = (double)jsonObject["USD_" + currency]["val"];
|
||||
log.DoLogInfo("http://free.currencyconverterapi.com - Latest exchange rate for USD to " + currency + " is " + result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Dictionary<string, MarketInfo> GetMarketInfosFromFile(PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
Dictionary<string, MarketInfo> result = new Dictionary<string, MarketInfo>();
|
||||
|
||||
string marketInfoFilePath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar + "MarketInfo.json";
|
||||
if (File.Exists(marketInfoFilePath)) {
|
||||
try {
|
||||
result = JsonConvert.DeserializeObject<Dictionary<string, MarketInfo>>(System.IO.File.ReadAllText(marketInfoFilePath));
|
||||
} catch (Exception ex) {
|
||||
log.DoLogDebug(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
result = new Dictionary<string, MarketInfo>();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SaveMarketInfosToFile(Dictionary<string, MarketInfo> marketInfos, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketInfo.json", JsonConvert.SerializeObject(marketInfos));
|
||||
}
|
||||
|
||||
public static Dictionary<string, Market> GetMarketDataFromFile(PTMagicConfiguration systemConfiguration, LogHelper log, string platform, DateTime maxDateTime, string marketCaption) {
|
||||
Dictionary<string, Market> result = new Dictionary<string, Market>();
|
||||
|
||||
DirectoryInfo dataDirectory = new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + platform + Path.DirectorySeparatorChar);
|
||||
|
||||
// Get market files older than max datetime in descending order (newest file up top)
|
||||
List<FileInfo> marketFiles = dataDirectory.EnumerateFiles("MarketData*")
|
||||
.Select(x => { x.Refresh(); return x; })
|
||||
.Where(x => x.LastWriteTimeUtc <= maxDateTime)
|
||||
.ToArray().OrderByDescending(f => f.LastWriteTimeUtc).ToList();
|
||||
|
||||
FileInfo marketFile = null;
|
||||
if (marketFiles.Count > 0) {
|
||||
marketFile = marketFiles.First();
|
||||
|
||||
log.DoLogDebug(platform + " - " + marketCaption + " market data loaded (" + marketFile.LastWriteTimeUtc.ToString() + ")");
|
||||
} else {
|
||||
log.DoLogDebug(platform + " - Not able to load " + marketCaption + " market data. Loading next youngest market file instead.");
|
||||
|
||||
// Get market files younger than max datetime in ascending order (oldest file up top)
|
||||
marketFiles = dataDirectory.EnumerateFiles("MarketData*")
|
||||
.Select(x => { x.Refresh(); return x; })
|
||||
.Where(x => x.LastWriteTimeUtc >= maxDateTime)
|
||||
.ToArray().OrderBy(f => f.LastWriteTimeUtc).ToList();
|
||||
if (marketFiles.Count > 0) {
|
||||
marketFile = marketFiles.First();
|
||||
log.DoLogDebug(platform + " - " + marketCaption + " market data loaded (" + marketFile.LastWriteTimeUtc.ToString() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Get JSON object
|
||||
result = JsonConvert.DeserializeObject<Dictionary<string, Market>>(marketFile.OpenText().ReadToEnd());
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Dictionary<string, List<MarketTrendChange>> BuildMarketTrends(string platform, string mainMarket, List<string> marketList, string sortBy, bool isGlobal, Dictionary<string, List<MarketTrendChange>> output, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
|
||||
try {
|
||||
List<MarketTrend> marketTrends = systemConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.FindAll(mt => mt.Platform.Equals(platform, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (marketTrends.Count > 0) {
|
||||
Dictionary<string, Market> recentMarkets = BaseAnalyzer.GetMarketDataFromFile(systemConfiguration, log, platform, DateTime.Now.ToUniversalTime(), "Recent");
|
||||
|
||||
foreach (MarketTrend marketTrend in marketTrends) {
|
||||
if (platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
log.DoLogInfo(platform + " - Building market trend changes for '" + marketTrend.Name + "' on main market '" + mainMarket + "' with " + marketList.Count.ToString() + " markets...");
|
||||
} else {
|
||||
log.DoLogInfo(platform + " - Building market trend changes for '" + marketTrend.Name + "'...");
|
||||
}
|
||||
|
||||
Dictionary<string, Market> trendMarkets = BaseAnalyzer.GetMarketDataFromFile(systemConfiguration, log, platform, DateTime.Now.ToUniversalTime().AddMinutes(-marketTrend.TrendMinutes), marketTrend.Name);
|
||||
|
||||
List<MarketTrendChange> marketTrendChanges = BaseAnalyzer.GetMarketTrendChanges(platform, mainMarket, marketTrend, marketList, recentMarkets, trendMarkets, sortBy, isGlobal, systemConfiguration, log);
|
||||
|
||||
output.Add(marketTrend.Name, marketTrendChanges);
|
||||
|
||||
log.DoLogInfo(platform + " - " + marketTrendChanges.Count.ToString() + " Market trend changes built for '" + marketTrend.Name + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static List<MarketTrendChange> GetMarketTrendChanges(string platform, string mainMarket, MarketTrend marketTrend, List<string> marketList, Dictionary<string, Market> recentMarkets, Dictionary<string, Market> trendMarkets, string sortBy, bool isGlobal, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<MarketTrendChange> result = new List<MarketTrendChange>();
|
||||
|
||||
var sortedMarkets = new SortedDictionary<string, Market>(recentMarkets).OrderBy(m => m.Value.Position);
|
||||
if (sortBy.Equals("Volume")) {
|
||||
sortedMarkets = new SortedDictionary<string, Market>(recentMarkets).OrderByDescending(m => m.Value.Volume24h);
|
||||
}
|
||||
|
||||
int marketCount = 1;
|
||||
foreach (KeyValuePair<string, Market> recentMarketPair in sortedMarkets) {
|
||||
if (marketList.Count == 0 || marketList.Contains(recentMarketPair.Key)) {
|
||||
bool excludeMainCurrency = systemConfiguration.AnalyzerSettings.MarketAnalyzer.ExcludeMainCurrency;
|
||||
if (!marketTrend.ExcludeMainCurrency) {
|
||||
excludeMainCurrency = marketTrend.ExcludeMainCurrency;
|
||||
}
|
||||
|
||||
if (platform.Equals("CoinMarketCap", StringComparison.InvariantCulture) && excludeMainCurrency) {
|
||||
|
||||
// Check if this is the main currency (only for CoinMarketCap)
|
||||
if (recentMarketPair.Value.Symbol.Equals(mainMarket, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
|
||||
// If the current market is the main currency, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Market recentMarket = recentMarkets[recentMarketPair.Key];
|
||||
|
||||
if (trendMarkets.ContainsKey(recentMarketPair.Key)) {
|
||||
List<string> ignoredMarkets = SystemHelper.ConvertTokenStringToList(marketTrend.IgnoredMarkets, ",");
|
||||
if (ignoredMarkets.Contains(recentMarketPair.Value.Symbol)) {
|
||||
log.DoLogDebug(platform + " - Market trend '" + marketTrend.Name + "' for '" + recentMarketPair.Key + "' is ignored in this trend.");
|
||||
continue;
|
||||
}
|
||||
|
||||
List<string> allowedMarkets = SystemHelper.ConvertTokenStringToList(marketTrend.AllowedMarkets, ",");
|
||||
if (allowedMarkets.Count > 0 && !allowedMarkets.Contains(recentMarketPair.Value.Symbol)) {
|
||||
log.DoLogDebug(platform + " - Market trend '" + marketTrend.Name + "' for '" + recentMarketPair.Key + "' is not allowed in this trend.");
|
||||
continue;
|
||||
}
|
||||
|
||||
Market trendMarket = trendMarkets[recentMarketPair.Key];
|
||||
|
||||
if (trendMarket != null) {
|
||||
double recentMarketPrice = recentMarket.Price;
|
||||
double trendMarketPrice = trendMarket.Price;
|
||||
|
||||
if (!platform.Equals("CoinMarketCap", StringComparison.InvariantCulture) && marketTrend.TrendCurrency.Equals("Fiat", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
if (recentMarket.MainCurrencyPriceUSD > 0 && trendMarket.MainCurrencyPriceUSD > 0) {
|
||||
recentMarketPrice = recentMarketPrice * recentMarket.MainCurrencyPriceUSD;
|
||||
trendMarketPrice = trendMarketPrice * trendMarket.MainCurrencyPriceUSD;
|
||||
}
|
||||
}
|
||||
|
||||
double trendMarketChange = (recentMarketPrice - trendMarketPrice) / trendMarketPrice * 100;
|
||||
|
||||
MarketTrendChange mtc = new MarketTrendChange();
|
||||
mtc.MarketTrendName = marketTrend.Name;
|
||||
mtc.TrendMinutes = marketTrend.TrendMinutes;
|
||||
mtc.TrendChange = trendMarketChange;
|
||||
mtc.Market = recentMarket.Name;
|
||||
mtc.LastPrice = recentMarket.Price;
|
||||
mtc.Volume24h = recentMarket.Volume24h;
|
||||
mtc.TrendDateTime = DateTime.Now;
|
||||
|
||||
result.Add(mtc);
|
||||
|
||||
log.DoLogDebug(platform + " - Market trend '" + marketTrend.Name + "' for '" + recentMarketPair.Key + "' (Vol. " + recentMarket.Volume24h.ToString("#,#0.00") + ") is " + trendMarketChange.ToString("#,#0.00") + "% in " + SystemHelper.GetProperDurationTime(marketTrend.TrendMinutes * 60).ToLower() + ".");
|
||||
|
||||
marketCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (marketTrend.MaxMarkets > 0 && isGlobal) {
|
||||
int maxMarkets = (marketTrend.MaxMarkets <= result.Count) ? marketTrend.MaxMarkets : result.Count;
|
||||
|
||||
result = result.GetRange(0, maxMarkets);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Dictionary<string, double> BuildGlobalMarketTrends(Dictionary<string, List<MarketTrendChange>> globalMarketTrendChanges, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
Dictionary<string, double> result = new Dictionary<string, double>();
|
||||
|
||||
List<MarketTrend> marketTrends = systemConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends;
|
||||
if (marketTrends.Count > 0) {
|
||||
foreach (MarketTrend marketTrend in marketTrends) {
|
||||
log.DoLogInfo("Building market trend average for '" + marketTrend.Name + "'");
|
||||
|
||||
if (globalMarketTrendChanges.ContainsKey(marketTrend.Name)) {
|
||||
List<MarketTrendChange> marketTrendChanges = globalMarketTrendChanges[marketTrend.Name];
|
||||
if (marketTrendChanges != null && marketTrendChanges.Count > 0) {
|
||||
|
||||
double averageTrendChange = marketTrendChanges.Average(mtc => mtc.TrendChange);
|
||||
|
||||
result.Add(marketTrend.Name, averageTrendChange);
|
||||
|
||||
log.DoLogInfo("Built average market trend change '" + marketTrend.Name + "' (" + averageTrendChange.ToString("#,#0.00") + "% in " + marketTrend.TrendMinutes.ToString() + " minutes) for " + marketTrendChanges.Count.ToString() + " markets.");
|
||||
} else {
|
||||
result.Add(marketTrend.Name, 0);
|
||||
|
||||
log.DoLogWarn("No market trend changes found for '" + marketTrend.Name + "' - returning 0%");
|
||||
}
|
||||
} else {
|
||||
result.Add(marketTrend.Name, 0);
|
||||
|
||||
log.DoLogWarn("Market trend '" + marketTrend.Name + "' not found in globalMarketTrendChanges[] - returning 0%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
using Core.ProfitTrailer;
|
||||
using System.Net;
|
||||
|
||||
namespace Core.MarketAnalyzer {
|
||||
public class Binance : BaseAnalyzer {
|
||||
public static double GetMainCurrencyPrice(string mainMarket, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
double result = 0;
|
||||
|
||||
try {
|
||||
string baseUrl = "https://api.binance.com/api/v1/ticker/24hr?symbol=" + mainMarket + "USDT";
|
||||
|
||||
log.DoLogInfo("Binance - Getting main market price...");
|
||||
Newtonsoft.Json.Linq.JObject jsonObject = GetSimpleJsonObjectFromURL(baseUrl, log, false);
|
||||
if (jsonObject != null) {
|
||||
log.DoLogInfo("Binance - Market data received for " + mainMarket + "USDT");
|
||||
|
||||
result = (double)jsonObject.GetValue("lastPrice");
|
||||
log.DoLogInfo("Binance - Current price for " + mainMarket + "USDT: " + result.ToString("#,#0.00") + " USD");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> GetMarketData(string mainMarket, Dictionary<string, MarketInfo> marketInfos, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<string> result = new List<string>();
|
||||
|
||||
string lastMarket = "";
|
||||
Newtonsoft.Json.Linq.JObject lastTicker = null;
|
||||
try {
|
||||
string baseUrl = "https://api.binance.com/api/v1/ticker/24hr";
|
||||
|
||||
log.DoLogInfo("Binance - Getting market data...");
|
||||
Newtonsoft.Json.Linq.JArray jsonArray = GetSimpleJsonArrayFromURL(baseUrl, log);
|
||||
if (jsonArray.Count > 0) {
|
||||
double mainCurrencyPrice = 1;
|
||||
if (!mainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
mainCurrencyPrice = Binance.GetMainCurrencyPrice(mainMarket, systemConfiguration, log);
|
||||
}
|
||||
|
||||
log.DoLogInfo("Binance - Market data received for " + jsonArray.Count.ToString() + " currencies");
|
||||
|
||||
if (mainCurrencyPrice > 0) {
|
||||
Dictionary<string, Market> markets = new Dictionary<string, Market>();
|
||||
foreach (Newtonsoft.Json.Linq.JObject currencyTicker in jsonArray) {
|
||||
string marketName = currencyTicker["symbol"].ToString();
|
||||
if (marketName.EndsWith(mainMarket, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
|
||||
// Set last values in case any error occurs
|
||||
lastMarket = marketName;
|
||||
lastTicker = currencyTicker;
|
||||
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = marketName;
|
||||
market.Symbol = currencyTicker["symbol"].ToString();
|
||||
market.Price = SystemHelper.TextToDouble(currencyTicker["lastPrice"].ToString(), 0, "en-US");
|
||||
market.Volume24h = SystemHelper.TextToDouble(currencyTicker["quoteVolume"].ToString(), 0, "en-US");
|
||||
market.MainCurrencyPriceUSD = mainCurrencyPrice;
|
||||
|
||||
markets.Add(market.Name, market);
|
||||
|
||||
result.Add(market.Name);
|
||||
}
|
||||
}
|
||||
|
||||
Binance.CheckFirstSeenDates(markets, ref marketInfos, systemConfiguration, log);
|
||||
|
||||
BaseAnalyzer.SaveMarketInfosToFile(marketInfos, systemConfiguration, log);
|
||||
|
||||
Binance.CheckForMarketDataRecreation(mainMarket, markets, systemConfiguration, log);
|
||||
|
||||
DateTime fileDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(markets), fileDateTime, fileDateTime);
|
||||
|
||||
log.DoLogInfo("Binance - Market data saved for " + markets.Count.ToString() + " markets with " + mainMarket + ".");
|
||||
|
||||
FileHelper.CleanupFiles(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
log.DoLogInfo("Binance - Market data cleaned.");
|
||||
} else {
|
||||
log.DoLogError("Binance - Failed to get main market price for " + mainMarket + ".");
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
} catch (WebException ex) {
|
||||
if (ex.Response != null) {
|
||||
using (HttpWebResponse errorResponse = (HttpWebResponse)ex.Response) {
|
||||
using (StreamReader reader = new StreamReader(errorResponse.GetResponseStream())) {
|
||||
Dictionary<string, string> errorData = JsonConvert.DeserializeObject<Dictionary<string, string>>(reader.ReadToEnd());
|
||||
if (errorData != null) {
|
||||
string errorMessage = "Unable to get data from Binance with URL '" + errorResponse.ResponseUri + "'!";
|
||||
if (errorData.ContainsKey("code")) {
|
||||
errorMessage += " - Code: " + errorData["code"];
|
||||
}
|
||||
|
||||
if (errorData.ContainsKey("msg")) {
|
||||
errorMessage += " - Message: " + errorData["msg"];
|
||||
}
|
||||
|
||||
log.DoLogError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Exception while getting data for '" + lastMarket + "': " + ex.Message, ex);
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CheckFirstSeenDates(Dictionary<string, Market> markets, ref Dictionary<string, MarketInfo> marketInfos, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
log.DoLogInfo("Binance - Checking first seen dates for " + markets.Count + " markets. This may take a while...");
|
||||
|
||||
int marketsChecked = 0;
|
||||
foreach (string key in markets.Keys) {
|
||||
// Save market info
|
||||
MarketInfo marketInfo = null;
|
||||
if (marketInfos.ContainsKey(key)) {
|
||||
marketInfo = marketInfos[key];
|
||||
}
|
||||
|
||||
if (marketInfo == null) {
|
||||
marketInfo = new MarketInfo();
|
||||
marketInfo.Name = key;
|
||||
marketInfos.Add(key, marketInfo);
|
||||
marketInfo.FirstSeen = Binance.GetFirstSeenDate(key, systemConfiguration, log);
|
||||
} else {
|
||||
if (marketInfo.FirstSeen == Constants.confMinDate) {
|
||||
marketInfo.FirstSeen = Binance.GetFirstSeenDate(key, systemConfiguration, log);
|
||||
}
|
||||
}
|
||||
marketInfo.LastSeen = DateTime.Now.ToUniversalTime();
|
||||
|
||||
marketsChecked++;
|
||||
|
||||
if ((marketsChecked % 20) == 0) {
|
||||
log.DoLogInfo("Binance - Yes, I am still checking first seen dates... " + marketsChecked + "/" + markets.Count + " markets done...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime GetFirstSeenDate(string marketName, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
DateTime result = Constants.confMinDate;
|
||||
|
||||
string baseUrl = "https://api.binance.com/api/v1/klines?interval=1d&symbol=" + marketName + "&limit=100";
|
||||
|
||||
log.DoLogDebug("Binance - Getting first seen date for '" + marketName + "'...");
|
||||
|
||||
Newtonsoft.Json.Linq.JArray jsonArray = GetSimpleJsonArrayFromURL(baseUrl, log);
|
||||
if (jsonArray.Count > 0) {
|
||||
result = Constants.Epoch.AddMilliseconds((Int64)jsonArray[0][0]);
|
||||
log.DoLogDebug("Binance - First seen date for '" + marketName + "' set to " + result.ToString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<MarketTick> GetMarketTicks(string marketName, int ticksNeeded, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<MarketTick> result = new List<MarketTick>();
|
||||
|
||||
try {
|
||||
Int64 endTime = (Int64)Math.Ceiling(DateTime.Now.ToUniversalTime().Subtract(Constants.Epoch).TotalMilliseconds);
|
||||
int ticksLimit = 500;
|
||||
string baseUrl = "";
|
||||
int ticksFetched = 0;
|
||||
|
||||
if (ticksNeeded < ticksLimit) {
|
||||
ticksLimit = ticksNeeded;
|
||||
}
|
||||
|
||||
bool go = true;
|
||||
while (ticksFetched < ticksNeeded && go) {
|
||||
baseUrl = "https://api.binance.com/api/v1/klines?interval=1m&symbol=" + marketName + "&endTime=" + endTime.ToString() + "&limit=" + ticksLimit.ToString();
|
||||
|
||||
log.DoLogDebug("Binance - Getting " + ticksLimit.ToString() + " ticks for '" + marketName + "'...");
|
||||
Newtonsoft.Json.Linq.JArray jsonArray = GetSimpleJsonArrayFromURL(baseUrl, log);
|
||||
if (jsonArray.Count > 0) {
|
||||
log.DoLogDebug("Binance - " + jsonArray.Count.ToString() + " ticks received.");
|
||||
|
||||
foreach (Newtonsoft.Json.Linq.JArray marketTick in jsonArray) {
|
||||
|
||||
MarketTick tick = new MarketTick();
|
||||
tick.Price = (double)marketTick[4];
|
||||
tick.Volume24h = (double)marketTick[7];
|
||||
tick.Time = Constants.Epoch.AddMilliseconds((Int64)marketTick[0]);
|
||||
|
||||
result.Add(tick);
|
||||
}
|
||||
|
||||
ticksFetched = ticksFetched + jsonArray.Count;
|
||||
endTime = endTime - ticksLimit * 60 * 1000;
|
||||
if (ticksNeeded - ticksFetched < ticksLimit) {
|
||||
ticksLimit = ticksNeeded - ticksFetched;
|
||||
}
|
||||
} else {
|
||||
log.DoLogDebug("Binance - No ticks received.");
|
||||
go = false;
|
||||
}
|
||||
}
|
||||
} catch (WebException ex) {
|
||||
if (ex.Response != null) {
|
||||
using (HttpWebResponse errorResponse = (HttpWebResponse)ex.Response) {
|
||||
using (StreamReader reader = new StreamReader(errorResponse.GetResponseStream())) {
|
||||
Dictionary<string, string> errorData = JsonConvert.DeserializeObject<Dictionary<string, string>>(reader.ReadToEnd());
|
||||
if (errorData != null) {
|
||||
string errorMessage = "Unable to get data from Binance with URL '" + errorResponse.ResponseUri + "'!";
|
||||
if (errorData.ContainsKey("code")) {
|
||||
errorMessage += " - Code: " + errorData["code"];
|
||||
}
|
||||
|
||||
if (errorData.ContainsKey("msg")) {
|
||||
errorMessage += " - Message: " + errorData["msg"];
|
||||
}
|
||||
|
||||
log.DoLogError(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CheckForMarketDataRecreation(string mainMarket, Dictionary<string, Market> markets, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
string binanceDataDirectoryPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar;
|
||||
|
||||
if (!Directory.Exists(binanceDataDirectoryPath)) {
|
||||
Directory.CreateDirectory(binanceDataDirectoryPath);
|
||||
}
|
||||
|
||||
DirectoryInfo dataDirectory = new DirectoryInfo(binanceDataDirectoryPath);
|
||||
|
||||
// Check for existing market files
|
||||
DateTime latestMarketDataFileDateTime = Constants.confMinDate;
|
||||
List<FileInfo> marketFiles = dataDirectory.EnumerateFiles("MarketData*").ToList();
|
||||
FileInfo latestMarketDataFile = null;
|
||||
if (marketFiles.Count > 0) {
|
||||
latestMarketDataFile = marketFiles.OrderByDescending(mdf => mdf.LastWriteTimeUtc).First();
|
||||
latestMarketDataFileDateTime = latestMarketDataFile.LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
if (latestMarketDataFileDateTime < DateTime.Now.ToUniversalTime().AddMinutes(-20)) {
|
||||
int lastMarketDataAgeInSeconds = (int)Math.Ceiling(DateTime.Now.ToUniversalTime().Subtract(latestMarketDataFileDateTime).TotalSeconds);
|
||||
|
||||
// Go back in time and create market data
|
||||
DateTime startDateTime = DateTime.Now.ToUniversalTime();
|
||||
DateTime endDateTime = DateTime.Now.ToUniversalTime().AddHours(-systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
if (latestMarketDataFileDateTime != Constants.confMinDate && latestMarketDataFileDateTime > endDateTime) {
|
||||
// Existing market files too old => Recreate market data for configured timeframe
|
||||
log.DoLogInfo("Binance - Recreating market data for " + markets.Count + " markets over " + SystemHelper.GetProperDurationTime(lastMarketDataAgeInSeconds) + ". This may take a while...");
|
||||
endDateTime = latestMarketDataFileDateTime;
|
||||
} else {
|
||||
// No existing market files found => Recreate market data for configured timeframe
|
||||
log.DoLogInfo("Binance - Recreating market data for " + markets.Count + " markets over " + systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours + " hours. This may take a while...");
|
||||
}
|
||||
|
||||
int totalTicks = (int)Math.Ceiling(startDateTime.Subtract(endDateTime).TotalMinutes);
|
||||
|
||||
// Get Ticks for main market
|
||||
List<MarketTick> mainMarketTicks = new List<MarketTick>();
|
||||
if (!mainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
mainMarketTicks = Binance.GetMarketTicks(mainMarket + "USDT", totalTicks, systemConfiguration, log);
|
||||
}
|
||||
|
||||
// Get Ticks for all markets
|
||||
log.DoLogDebug("Binance - Getting ticks for '" + markets.Count + "' markets");
|
||||
Dictionary<string, List<MarketTick>> marketTicks = new Dictionary<string, List<MarketTick>>();
|
||||
foreach (string key in markets.Keys) {
|
||||
marketTicks.Add(key, Binance.GetMarketTicks(key, totalTicks, systemConfiguration, log));
|
||||
|
||||
if ((marketTicks.Count % 10) == 0) {
|
||||
log.DoLogInfo("Binance - No worries, I am still alive... " + marketTicks.Count + "/" + markets.Count + " markets done...");
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Binance - Ticks completed.");
|
||||
|
||||
log.DoLogInfo("Binance - Creating initial market data ticks. This may take another while...");
|
||||
|
||||
// Go back in time and create market data
|
||||
int completedTicks = 0;
|
||||
if (marketTicks.Count > 0) {
|
||||
for (DateTime tickTime = startDateTime; tickTime >= endDateTime; tickTime = tickTime.AddMinutes(-1)) {
|
||||
completedTicks++;
|
||||
|
||||
double mainCurrencyPrice = 1;
|
||||
if (mainMarketTicks.Count > 0) {
|
||||
List<MarketTick> mainCurrencyTickRange = mainMarketTicks.FindAll(t => t.Time <= tickTime);
|
||||
if (mainCurrencyTickRange.Count > 0) {
|
||||
MarketTick mainCurrencyTick = mainCurrencyTickRange.OrderByDescending(t => t.Time).First();
|
||||
mainCurrencyPrice = mainCurrencyTick.Price;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, Market> tickMarkets = new Dictionary<string, Market>();
|
||||
foreach (string key in markets.Keys) {
|
||||
List<MarketTick> tickRange = marketTicks[key].FindAll(t => t.Time <= tickTime);
|
||||
|
||||
if (tickRange.Count > 0) {
|
||||
MarketTick marketTick = tickRange.OrderByDescending(t => t.Time).First();
|
||||
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = key;
|
||||
market.Symbol = key;
|
||||
market.Price = marketTick.Price;
|
||||
//market.Volume24h = marketTick.Volume24h;
|
||||
market.MainCurrencyPriceUSD = mainCurrencyPrice;
|
||||
|
||||
tickMarkets.Add(market.Name, market);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime fileDateTime = new DateTime(tickTime.ToLocalTime().Year, tickTime.ToLocalTime().Month, tickTime.ToLocalTime().Day, tickTime.ToLocalTime().Hour, tickTime.ToLocalTime().Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(tickMarkets), fileDateTime, fileDateTime);
|
||||
|
||||
log.DoLogDebug("Binance - Market data saved for tick " + fileDateTime.ToString() + " - MainCurrencyPrice=" + mainCurrencyPrice.ToString("#,#0.00") + " USD.");
|
||||
|
||||
if ((completedTicks % 100) == 0) {
|
||||
log.DoLogInfo("Binance - Our magicbots are still at work, hang on... " + completedTicks + "/" + totalTicks + " ticks done...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Binance - Initial market data created. Ready to go!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
using Core.ProfitTrailer;
|
||||
|
||||
namespace Core.MarketAnalyzer {
|
||||
public class Bittrex : BaseAnalyzer {
|
||||
public static double GetMainCurrencyPrice(string mainMarket, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
double result = 0;
|
||||
|
||||
try {
|
||||
string baseUrl = "https://bittrex.com/api/v1.1/public/getmarketsummary?market=USDT-" + mainMarket;
|
||||
|
||||
log.DoLogInfo("Bittrex - Getting main market price...");
|
||||
Dictionary<string, dynamic> jsonObject = GetJsonFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
if (jsonObject["success"]) {
|
||||
log.DoLogInfo("Bittrex - Market data received for USDT-" + mainMarket);
|
||||
|
||||
result = jsonObject["result"][0]["Last"];
|
||||
log.DoLogInfo("Bittrex - Current price for USDT-" + mainMarket + ": " + result.ToString("#,#0.00") + " USD");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> GetMarketData(string mainMarket, Dictionary<string, MarketInfo> marketInfos, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<string> result = new List<string>();
|
||||
|
||||
string lastMarket = "";
|
||||
Newtonsoft.Json.Linq.JObject lastTicker = null;
|
||||
try {
|
||||
string baseUrl = "https://bittrex.com/api/v2.0/pub/markets/GetMarketSummaries";
|
||||
|
||||
log.DoLogInfo("Bittrex - Getting market data...");
|
||||
Dictionary<string, dynamic> jsonObject = GetJsonFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
if (jsonObject["success"]) {
|
||||
log.DoLogInfo("Bittrex - Market data received for " + jsonObject["result"].Count.ToString() + " currencies");
|
||||
|
||||
double mainCurrencyPrice = 1;
|
||||
if (!mainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
mainCurrencyPrice = Bittrex.GetMainCurrencyPrice(mainMarket, systemConfiguration, log);
|
||||
}
|
||||
|
||||
if (mainCurrencyPrice > 0) {
|
||||
Dictionary<string, Market> markets = new Dictionary<string, Market>();
|
||||
foreach (Newtonsoft.Json.Linq.JObject currencyTicker in jsonObject["result"]) {
|
||||
string marketName = currencyTicker["Summary"]["MarketName"].ToString();
|
||||
if (marketName.StartsWith(mainMarket, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
|
||||
// Set last values in case any error occurs
|
||||
lastMarket = marketName;
|
||||
lastTicker = currencyTicker;
|
||||
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = marketName;
|
||||
market.Symbol = currencyTicker["Summary"]["MarketName"].ToString();
|
||||
if (currencyTicker["Summary"]["Last"].Type == Newtonsoft.Json.Linq.JTokenType.Float) market.Price = (double)currencyTicker["Summary"]["Last"];
|
||||
if (currencyTicker["Summary"]["BaseVolume"].Type == Newtonsoft.Json.Linq.JTokenType.Float) market.Volume24h = (double)currencyTicker["Summary"]["BaseVolume"];
|
||||
market.MainCurrencyPriceUSD = mainCurrencyPrice;
|
||||
|
||||
markets.Add(market.Name, market);
|
||||
|
||||
result.Add(market.Name);
|
||||
|
||||
// Save market info
|
||||
MarketInfo marketInfo = null;
|
||||
if (marketInfos.ContainsKey(marketName)) {
|
||||
marketInfo = marketInfos[marketName];
|
||||
}
|
||||
|
||||
if (marketInfo == null) {
|
||||
marketInfo = new MarketInfo();
|
||||
marketInfo.Name = marketName;
|
||||
marketInfos.Add(marketName, marketInfo);
|
||||
}
|
||||
if (currencyTicker["Summary"]["Created"].Type == Newtonsoft.Json.Linq.JTokenType.Date) marketInfo.FirstSeen = (DateTime)currencyTicker["Summary"]["Created"];
|
||||
marketInfo.LastSeen = DateTime.Now.ToUniversalTime();
|
||||
}
|
||||
}
|
||||
|
||||
BaseAnalyzer.SaveMarketInfosToFile(marketInfos, systemConfiguration, log);
|
||||
|
||||
Bittrex.CheckForMarketDataRecreation(mainMarket, markets, systemConfiguration, log);
|
||||
|
||||
DateTime fileDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(markets), fileDateTime, fileDateTime);
|
||||
|
||||
log.DoLogInfo("Bittrex - Market data saved for " + markets.Count.ToString() + " markets with " + mainMarket + ".");
|
||||
|
||||
FileHelper.CleanupFiles(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
log.DoLogInfo("Bittrex - Market data cleaned.");
|
||||
} else {
|
||||
log.DoLogError("Bittrex - Failed to get main market price for " + mainMarket + ".");
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Exception while getting data for '" + lastMarket + "': " + ex.Message, ex);
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<MarketTick> GetMarketTicks(string marketName, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<MarketTick> result = new List<MarketTick>();
|
||||
|
||||
try {
|
||||
string baseUrl = "https://bittrex.com/Api/v2.0/pub/market/GetTicks?tickInterval=oneMin&marketName=" + marketName;
|
||||
|
||||
log.DoLogDebug("Bittrex - Getting ticks for '" + marketName + "'...");
|
||||
Dictionary<string, dynamic> jsonObject = GetJsonFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
if (jsonObject["success"]) {
|
||||
if (jsonObject["result"] != null) {
|
||||
log.DoLogDebug("Bittrex - " + jsonObject["result"].Count.ToString() + " ticks received.");
|
||||
|
||||
foreach (var marketTick in jsonObject["result"]) {
|
||||
|
||||
MarketTick tick = new MarketTick();
|
||||
tick.Price = (double)marketTick["C"];
|
||||
tick.Time = SystemHelper.TextToDateTime(marketTick["T"].ToString(), Constants.confMinDate);
|
||||
|
||||
result.Add(tick);
|
||||
}
|
||||
} else {
|
||||
log.DoLogDebug("Bittrex - No ticks received.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CheckForMarketDataRecreation(string mainMarket, Dictionary<string, Market> markets, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
string bittrexDataDirectoryPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar;
|
||||
|
||||
if (!Directory.Exists(bittrexDataDirectoryPath)) {
|
||||
Directory.CreateDirectory(bittrexDataDirectoryPath);
|
||||
}
|
||||
|
||||
DirectoryInfo dataDirectory = new DirectoryInfo(bittrexDataDirectoryPath);
|
||||
|
||||
// Check for existing market files
|
||||
DateTime latestMarketDataFileDateTime = Constants.confMinDate;
|
||||
List<FileInfo> marketFiles = dataDirectory.EnumerateFiles("MarketData*").ToList();
|
||||
FileInfo latestMarketDataFile = null;
|
||||
if (marketFiles.Count > 0) {
|
||||
latestMarketDataFile = marketFiles.OrderByDescending(mdf => mdf.LastWriteTimeUtc).First();
|
||||
latestMarketDataFileDateTime = latestMarketDataFile.LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
if (latestMarketDataFileDateTime < DateTime.Now.ToUniversalTime().AddMinutes(-20)) {
|
||||
int lastMarketDataAgeInSeconds = (int)Math.Ceiling(DateTime.Now.ToUniversalTime().Subtract(latestMarketDataFileDateTime).TotalSeconds);
|
||||
|
||||
// Go back in time and create market data
|
||||
DateTime startDateTime = DateTime.Now.ToUniversalTime();
|
||||
DateTime endDateTime = DateTime.Now.ToUniversalTime().AddHours(-systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
if (latestMarketDataFileDateTime != Constants.confMinDate && latestMarketDataFileDateTime > endDateTime) {
|
||||
// Existing market files too old => Recreate market data for configured timeframe
|
||||
log.DoLogInfo("Bittrex - Recreating market data for " + markets.Count + " markets over " + SystemHelper.GetProperDurationTime(lastMarketDataAgeInSeconds) + ". This may take a while...");
|
||||
endDateTime = latestMarketDataFileDateTime;
|
||||
} else {
|
||||
// No existing market files found => Recreate market data for configured timeframe
|
||||
log.DoLogInfo("Bittrex - Recreating market data for " + markets.Count + " markets over " + systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours + " hours. This may take a while...");
|
||||
}
|
||||
|
||||
// Get Ticks for main market
|
||||
List<MarketTick> mainMarketTicks = new List<MarketTick>();
|
||||
if (!mainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
mainMarketTicks = Bittrex.GetMarketTicks("USDT-" + mainMarket, systemConfiguration, log);
|
||||
}
|
||||
|
||||
// Get Ticks for all markets
|
||||
log.DoLogDebug("Bittrex - Getting ticks for '" + markets.Count + "' markets");
|
||||
Dictionary<string, List<MarketTick>> marketTicks = new Dictionary<string, List<MarketTick>>();
|
||||
foreach (string key in markets.Keys) {
|
||||
marketTicks.Add(key, Bittrex.GetMarketTicks(key, systemConfiguration, log));
|
||||
|
||||
if ((marketTicks.Count % 10) == 0) {
|
||||
log.DoLogInfo("Bittrex - No worries, I am still alive... " + marketTicks.Count + "/" + markets.Count + " markets done...");
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Bittrex - Ticks completed.");
|
||||
|
||||
log.DoLogInfo("Bittrex - Creating initial market data ticks. This may take another while...");
|
||||
|
||||
int totalTicks = (int)Math.Ceiling(startDateTime.Subtract(endDateTime).TotalMinutes);
|
||||
int completedTicks = 0;
|
||||
if (marketTicks.Count > 0) {
|
||||
for (DateTime tickTime = startDateTime.ToUniversalTime(); tickTime >= endDateTime.ToUniversalTime(); tickTime = tickTime.AddMinutes(-1)) {
|
||||
completedTicks++;
|
||||
|
||||
double mainCurrencyPrice = 1;
|
||||
if (mainMarketTicks.Count > 0) {
|
||||
List<MarketTick> mainCurrencyTickRange = mainMarketTicks.FindAll(t => t.Time <= tickTime);
|
||||
if (mainCurrencyTickRange.Count > 0) {
|
||||
MarketTick mainCurrencyTick = mainCurrencyTickRange.OrderByDescending(t => t.Time).First();
|
||||
mainCurrencyPrice = mainCurrencyTick.Price;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, Market> tickMarkets = new Dictionary<string, Market>();
|
||||
foreach (string key in markets.Keys) {
|
||||
List<MarketTick> tickRange = marketTicks[key].FindAll(t => t.Time <= tickTime);
|
||||
|
||||
if (tickRange.Count > 0) {
|
||||
MarketTick marketTick = tickRange.OrderByDescending(t => t.Time).First();
|
||||
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = key;
|
||||
market.Symbol = key;
|
||||
market.Price = marketTick.Price;
|
||||
//market.Volume24h = marketTick.Volume24h;
|
||||
market.MainCurrencyPriceUSD = mainCurrencyPrice;
|
||||
|
||||
tickMarkets.Add(market.Name, market);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime fileDateTime = new DateTime(tickTime.ToLocalTime().Year, tickTime.ToLocalTime().Month, tickTime.ToLocalTime().Day, tickTime.ToLocalTime().Hour, tickTime.ToLocalTime().Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(tickMarkets), fileDateTime, fileDateTime);
|
||||
|
||||
log.DoLogDebug("Bittrex - Market data saved for tick " + fileDateTime.ToString() + " - MainCurrencyPrice=" + mainCurrencyPrice.ToString("#,#0.00") + " USD.");
|
||||
|
||||
if ((completedTicks % 100) == 0) {
|
||||
log.DoLogInfo("Bittrex - Our magicbots are still at work, hang on... " + completedTicks + "/" + totalTicks + " ticks done...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Bittrex - Initial market data created. Ready to go!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Core.MarketAnalyzer {
|
||||
public class CoinMarketCap : BaseAnalyzer {
|
||||
public static string GetMarketData(PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
string result = "";
|
||||
try {
|
||||
string baseUrl = "https://api.coinmarketcap.com/v2/ticker/";
|
||||
|
||||
log.DoLogInfo("CoinMarketCap - Getting market data...");
|
||||
|
||||
Dictionary<string, dynamic> jsonObject = GetJsonFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
if (jsonObject["data"] != null) {
|
||||
Newtonsoft.Json.Linq.JObject jsonDataObject = (Newtonsoft.Json.Linq.JObject)jsonObject["data"];
|
||||
log.DoLogInfo("CoinMarketCap - Market data received for " + jsonDataObject.Count.ToString() + " currencies");
|
||||
|
||||
Dictionary<string, Market> markets = new Dictionary<string, Market>();
|
||||
foreach (Newtonsoft.Json.Linq.JToken currencyTicker in jsonDataObject.Children()) {
|
||||
|
||||
if (currencyTicker.First["quotes"] != null) {
|
||||
|
||||
if (currencyTicker.First["quotes"]["USD"] != null) {
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = currencyTicker.First["name"].ToString();
|
||||
market.Symbol = currencyTicker.First["symbol"].ToString();
|
||||
market.Price = (double)currencyTicker.First["quotes"]["USD"]["price"];
|
||||
market.Volume24h = (double)currencyTicker.First["quotes"]["USD"]["volume_24h"];
|
||||
if (currencyTicker.First["quotes"]["USD"]["percent_change_24h"] != null) {
|
||||
market.TrendChange24h = (double)currencyTicker.First["quotes"]["USD"]["percent_change_24h"];
|
||||
}
|
||||
|
||||
markets.Add(market.Name, market);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CoinMarketCap.CheckForMarketDataRecreation(markets, systemConfiguration, log);
|
||||
|
||||
DateTime fileDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathCoinMarketCap + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(markets), fileDateTime, fileDateTime);
|
||||
|
||||
|
||||
log.DoLogInfo("CoinMarketCap - Market data saved.");
|
||||
|
||||
FileHelper.CleanupFiles(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathCoinMarketCap + Path.DirectorySeparatorChar, systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
log.DoLogInfo("CoinMarketCap - Market data cleaned.");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
result = ex.Message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CheckForMarketDataRecreation(Dictionary<string, Market> markets, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
string coinMarketCapDataDirectoryPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathCoinMarketCap + Path.DirectorySeparatorChar;
|
||||
|
||||
if (!Directory.Exists(coinMarketCapDataDirectoryPath)) {
|
||||
Directory.CreateDirectory(coinMarketCapDataDirectoryPath);
|
||||
}
|
||||
|
||||
DirectoryInfo dataDirectory = new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathCoinMarketCap + Path.DirectorySeparatorChar);
|
||||
|
||||
List<FileInfo> marketFiles = dataDirectory.EnumerateFiles("MarketData*")
|
||||
.Select(x => { x.Refresh(); return x; })
|
||||
.Where(x => x.LastWriteTimeUtc <= DateTime.Now.AddHours(-24))
|
||||
.ToArray().OrderByDescending(f => f.LastWriteTimeUtc).ToList();
|
||||
|
||||
bool build24hMarketDataFile = false;
|
||||
FileInfo marketFile = null;
|
||||
if (marketFiles.Count > 0) {
|
||||
marketFile = marketFiles.First();
|
||||
if (marketFile.LastWriteTimeUtc <= DateTime.Now.AddHours(-24).AddMinutes(-systemConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes).AddSeconds(-10)) {
|
||||
log.DoLogDebug("CoinMarketCap - 24h market data file too old (" + marketFile.LastWriteTimeUtc.ToString() + "). Rebuilding data...");
|
||||
build24hMarketDataFile = true;
|
||||
}
|
||||
} else {
|
||||
marketFiles = dataDirectory.EnumerateFiles("MarketData*")
|
||||
.Select(x => { x.Refresh(); return x; })
|
||||
.Where(x => x.LastWriteTimeUtc >= DateTime.Now.AddHours(-24))
|
||||
.ToArray().OrderBy(f => f.LastWriteTimeUtc).ToList();
|
||||
|
||||
if (marketFiles.Count > 0) {
|
||||
marketFile = marketFiles.First();
|
||||
if (marketFile.LastWriteTimeUtc >= DateTime.Now.AddHours(-24).AddMinutes(systemConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes).AddSeconds(10)) {
|
||||
log.DoLogDebug("CoinMarketCap - 24h market data file too young (" + marketFile.LastWriteTimeUtc.ToString() + "). Rebuilding data...");
|
||||
build24hMarketDataFile = true;
|
||||
}
|
||||
} else {
|
||||
log.DoLogDebug("CoinMarketCap - 24h market data not found. Rebuilding data...");
|
||||
build24hMarketDataFile = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (build24hMarketDataFile) {
|
||||
Dictionary<string, Market> markets24h = new Dictionary<string, Market>();
|
||||
foreach (string key in markets.Keys) {
|
||||
Market market24h = new Market();
|
||||
market24h.Position = markets.Count + 1;
|
||||
market24h.Name = markets[key].Name;
|
||||
market24h.Symbol = markets[key].Symbol;
|
||||
market24h.Price = markets[key].Price / (1 + (markets[key].TrendChange24h / 100));
|
||||
market24h.Volume24h = markets[key].Volume24h;
|
||||
|
||||
markets24h.Add(markets[key].Name, market24h);
|
||||
}
|
||||
|
||||
DateTime fileDateTime = new DateTime(DateTime.Now.ToLocalTime().AddHours(-24).Year, DateTime.Now.ToLocalTime().AddHours(-24).Month, DateTime.Now.ToLocalTime().AddHours(-24).Day, DateTime.Now.ToLocalTime().AddHours(-24).Hour, DateTime.Now.ToLocalTime().AddHours(-24).Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathCoinMarketCap + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(markets24h), fileDateTime, fileDateTime);
|
||||
|
||||
|
||||
log.DoLogInfo("CoinMarketCap - 24h market data rebuilt.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
using Core.ProfitTrailer;
|
||||
|
||||
namespace Core.MarketAnalyzer {
|
||||
public class Poloniex : BaseAnalyzer {
|
||||
public static double GetMainCurrencyPrice(string mainMarket, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
double result = 0;
|
||||
|
||||
try {
|
||||
string baseUrl = "https://bittrex.com/api/v1.1/public/getmarketsummary?market=USDT-" + mainMarket;
|
||||
|
||||
log.DoLogInfo("Poloniex - Getting main market price...");
|
||||
Dictionary<string, dynamic> jsonObject = GetJsonFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
if (jsonObject["success"]) {
|
||||
log.DoLogInfo("Poloniex - Market data received for USDT_" + mainMarket);
|
||||
|
||||
result = jsonObject["result"][0]["Last"];
|
||||
log.DoLogInfo("Poloniex - Current price for USDT_" + mainMarket + ": " + result.ToString("#,#0.00") + " USD");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> GetMarketData(string mainMarket, Dictionary<string, MarketInfo> marketInfos, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<string> result = new List<string>();
|
||||
|
||||
string lastMarket = "";
|
||||
KeyValuePair<string, dynamic> lastTicker = new KeyValuePair<string, dynamic>();
|
||||
try {
|
||||
string baseUrl = "https://poloniex.com/public?command=returnTicker";
|
||||
|
||||
log.DoLogInfo("Poloniex - Getting market data...");
|
||||
Dictionary<string, dynamic> jsonObject = GetJsonFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
log.DoLogInfo("Poloniex - Market data received for " + jsonObject.Count.ToString() + " currencies");
|
||||
|
||||
double mainCurrencyPrice = 1;
|
||||
if (!mainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
mainCurrencyPrice = Poloniex.GetMainCurrencyPrice(mainMarket, systemConfiguration, log);
|
||||
}
|
||||
|
||||
if (mainCurrencyPrice > 0) {
|
||||
Dictionary<string, Market> markets = new Dictionary<string, Market>();
|
||||
foreach (KeyValuePair<string, dynamic> currencyTicker in jsonObject) {
|
||||
string marketName = currencyTicker.Key.ToString();
|
||||
if (marketName.StartsWith(mainMarket, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
|
||||
// Set last values in case any error occurs
|
||||
lastMarket = marketName;
|
||||
lastTicker = currencyTicker;
|
||||
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = marketName;
|
||||
market.Symbol = currencyTicker.Key.ToString();
|
||||
market.Price = SystemHelper.TextToDouble(currencyTicker.Value["last"].ToString(), 0.0, "en-US");
|
||||
market.Volume24h = SystemHelper.TextToDouble(currencyTicker.Value["baseVolume"].ToString(), 0.0, "en-US");
|
||||
market.MainCurrencyPriceUSD = mainCurrencyPrice;
|
||||
|
||||
markets.Add(market.Name, market);
|
||||
|
||||
result.Add(market.Name);
|
||||
}
|
||||
}
|
||||
|
||||
Poloniex.CheckFirstSeenDates(markets, ref marketInfos, systemConfiguration, log);
|
||||
|
||||
BaseAnalyzer.SaveMarketInfosToFile(marketInfos, systemConfiguration, log);
|
||||
|
||||
Poloniex.CheckForMarketDataRecreation(mainMarket, markets, systemConfiguration, log);
|
||||
|
||||
DateTime fileDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(markets), fileDateTime, fileDateTime);
|
||||
|
||||
|
||||
log.DoLogInfo("Poloniex - Market data saved for " + markets.Count.ToString() + " markets with " + mainMarket + ".");
|
||||
|
||||
FileHelper.CleanupFiles(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
log.DoLogInfo("Poloniex - Market data cleaned.");
|
||||
} else {
|
||||
log.DoLogError("Poloniex - Failed to get main market price for " + mainMarket + ".");
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Exception while getting data for '" + lastMarket + "': " + ex.Message, ex);
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CheckFirstSeenDates(Dictionary<string, Market> markets, ref Dictionary<string, MarketInfo> marketInfos, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
log.DoLogInfo("Poloniex - Checking first seen dates for " + markets.Count + " markets. This may take a while...");
|
||||
|
||||
int marketsChecked = 0;
|
||||
foreach (string key in markets.Keys) {
|
||||
// Save market info
|
||||
MarketInfo marketInfo = null;
|
||||
if (marketInfos.ContainsKey(key)) {
|
||||
marketInfo = marketInfos[key];
|
||||
}
|
||||
if (marketInfo == null) {
|
||||
marketInfo = new MarketInfo();
|
||||
marketInfo.Name = key;
|
||||
marketInfos.Add(key, marketInfo);
|
||||
marketInfo.FirstSeen = Poloniex.GetFirstSeenDate(key, systemConfiguration, log);
|
||||
} else {
|
||||
if (marketInfo.FirstSeen == Constants.confMinDate) {
|
||||
marketInfo.FirstSeen = Poloniex.GetFirstSeenDate(key, systemConfiguration, log);
|
||||
}
|
||||
}
|
||||
marketInfo.LastSeen = DateTime.Now.ToUniversalTime();
|
||||
|
||||
marketsChecked++;
|
||||
|
||||
if ((marketsChecked % 20) == 0) {
|
||||
log.DoLogInfo("Poloniex - Yes, I am still checking first seen dates... " + marketsChecked + "/" + markets.Count + " markets done...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DateTime GetFirstSeenDate(string marketName, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
DateTime result = Constants.confMinDate;
|
||||
|
||||
Int64 startTime = (Int64)Math.Ceiling(DateTime.Now.ToUniversalTime().AddDays(-100).Subtract(Constants.Epoch).TotalSeconds);
|
||||
string baseUrl = "https://poloniex.com/public?command=returnChartData&period=14400&start=" + startTime.ToString() + "&end=9999999999¤cyPair=" + marketName;
|
||||
|
||||
log.DoLogDebug("Poloniex - Getting first seen date for '" + marketName + "'...");
|
||||
|
||||
List<dynamic> jsonObject = GetSimpleJsonListFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
var marketTick = jsonObject[0];
|
||||
|
||||
result = Constants.Epoch.AddSeconds((int)marketTick["date"]);
|
||||
log.DoLogDebug("Poloniex - First seen date for '" + marketName + "' set to " + result.ToString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<MarketTick> GetMarketTicks(string marketName, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<MarketTick> result = new List<MarketTick>();
|
||||
|
||||
try {
|
||||
Int64 startTime = (Int64)Math.Ceiling(DateTime.Now.ToUniversalTime().AddHours(-systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours).Subtract(Constants.Epoch).TotalSeconds);
|
||||
string baseUrl = "https://poloniex.com/public?command=returnChartData&period=300&start=" + startTime.ToString() + "&end=9999999999¤cyPair=" + marketName;
|
||||
|
||||
log.DoLogDebug("Poloniex - Getting ticks for '" + marketName + "'...");
|
||||
List<dynamic> jsonObject = GetSimpleJsonListFromURL(baseUrl, log);
|
||||
if (jsonObject.Count > 0) {
|
||||
log.DoLogDebug("Poloniex - " + jsonObject.Count.ToString() + " ticks received.");
|
||||
|
||||
foreach (var marketTick in jsonObject) {
|
||||
|
||||
MarketTick tick = new MarketTick();
|
||||
tick.Price = (double)marketTick["close"];
|
||||
tick.Time = Constants.Epoch.AddSeconds((int)marketTick["date"]);
|
||||
|
||||
result.Add(tick);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical(ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CheckForMarketDataRecreation(string mainMarket, Dictionary<string, Market> markets, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
string poloniexDataDirectoryPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar;
|
||||
|
||||
if (!Directory.Exists(poloniexDataDirectoryPath)) {
|
||||
Directory.CreateDirectory(poloniexDataDirectoryPath);
|
||||
}
|
||||
|
||||
DirectoryInfo dataDirectory = new DirectoryInfo(poloniexDataDirectoryPath);
|
||||
|
||||
// Check for existing market files
|
||||
DateTime latestMarketDataFileDateTime = Constants.confMinDate;
|
||||
List<FileInfo> marketFiles = dataDirectory.EnumerateFiles("MarketData*").ToList();
|
||||
FileInfo latestMarketDataFile = null;
|
||||
if (marketFiles.Count > 0) {
|
||||
latestMarketDataFile = marketFiles.OrderByDescending(mdf => mdf.LastWriteTimeUtc).First();
|
||||
latestMarketDataFileDateTime = latestMarketDataFile.LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
if (latestMarketDataFileDateTime < DateTime.Now.ToUniversalTime().AddMinutes(-(systemConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes * 3))) {
|
||||
int lastMarketDataAgeInSeconds = (int)Math.Ceiling(DateTime.Now.ToUniversalTime().Subtract(latestMarketDataFileDateTime).TotalSeconds);
|
||||
|
||||
// Go back in time and create market data
|
||||
DateTime startDateTime = DateTime.Now.ToUniversalTime();
|
||||
DateTime endDateTime = DateTime.Now.ToUniversalTime().AddHours(-systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
if (latestMarketDataFileDateTime != Constants.confMinDate && latestMarketDataFileDateTime > endDateTime) {
|
||||
// Existing market files too old => Recreate market data for configured timeframe
|
||||
log.DoLogInfo("Poloniex - Recreating market data for " + markets.Count + " markets over " + SystemHelper.GetProperDurationTime(lastMarketDataAgeInSeconds) + ". This may take a while...");
|
||||
endDateTime = latestMarketDataFileDateTime;
|
||||
} else {
|
||||
// No existing market files found => Recreate market data for configured timeframe
|
||||
log.DoLogInfo("Poloniex - Recreating market data for " + markets.Count + " markets over " + systemConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours + " hours. This may take a while...");
|
||||
}
|
||||
|
||||
// Get Ticks for main market
|
||||
List<MarketTick> mainMarketTicks = new List<MarketTick>();
|
||||
if (!mainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
mainMarketTicks = Poloniex.GetMarketTicks("USDT_" + mainMarket, systemConfiguration, log);
|
||||
}
|
||||
|
||||
// Get Ticks for all markets
|
||||
log.DoLogDebug("Poloniex - Getting ticks for '" + markets.Count + "' markets");
|
||||
Dictionary<string, List<MarketTick>> marketTicks = new Dictionary<string, List<MarketTick>>();
|
||||
foreach (string key in markets.Keys) {
|
||||
marketTicks.Add(key, Poloniex.GetMarketTicks(key, systemConfiguration, log));
|
||||
|
||||
if ((marketTicks.Count % 10) == 0) {
|
||||
log.DoLogInfo("Poloniex - No worries, I am still alive... " + marketTicks.Count + "/" + markets.Count + " markets done...");
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Poloniex - Ticks completed.");
|
||||
|
||||
log.DoLogInfo("Poloniex - Creating initial market data ticks. This may take another while...");
|
||||
|
||||
// Go back in time and create market data
|
||||
int totalTicks = (int)Math.Ceiling(startDateTime.Subtract(endDateTime).TotalMinutes);
|
||||
int completedTicks = 0;
|
||||
if (marketTicks.Count > 0) {
|
||||
for (DateTime tickTime = startDateTime; tickTime >= endDateTime; tickTime = tickTime.AddMinutes(-5)) {
|
||||
completedTicks++;
|
||||
|
||||
double mainCurrencyPrice = 1;
|
||||
if (mainMarketTicks.Count > 0) {
|
||||
List<MarketTick> mainCurrencyTickRange = mainMarketTicks.FindAll(t => t.Time <= tickTime);
|
||||
if (mainCurrencyTickRange.Count > 0) {
|
||||
MarketTick mainCurrencyTick = mainCurrencyTickRange.OrderByDescending(t => t.Time).First();
|
||||
mainCurrencyPrice = mainCurrencyTick.Price;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, Market> tickMarkets = new Dictionary<string, Market>();
|
||||
foreach (string key in markets.Keys) {
|
||||
List<MarketTick> tickRange = marketTicks[key].FindAll(t => t.Time <= tickTime);
|
||||
|
||||
if (tickRange.Count > 0) {
|
||||
MarketTick marketTick = tickRange.OrderByDescending(t => t.Time).First();
|
||||
|
||||
Market market = new Market();
|
||||
market.Position = markets.Count + 1;
|
||||
market.Name = key;
|
||||
market.Symbol = key;
|
||||
market.Price = marketTick.Price;
|
||||
//market.Volume24h = marketTick.Volume24h;
|
||||
market.MainCurrencyPriceUSD = mainCurrencyPrice;
|
||||
|
||||
tickMarkets.Add(market.Name, market);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime fileDateTime = new DateTime(tickTime.ToLocalTime().Year, tickTime.ToLocalTime().Month, tickTime.ToLocalTime().Day, tickTime.ToLocalTime().Hour, tickTime.ToLocalTime().Minute, 0).ToUniversalTime();
|
||||
|
||||
FileHelper.WriteTextToFile(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathData + Path.DirectorySeparatorChar + Constants.PTMagicPathExchange + Path.DirectorySeparatorChar, "MarketData_" + fileDateTime.ToString("yyyy-MM-dd_HH.mm") + ".json", JsonConvert.SerializeObject(tickMarkets), fileDateTime, fileDateTime);
|
||||
|
||||
|
||||
log.DoLogDebug("Poloniex - Market data saved for tick " + tickTime.ToLocalTime().ToString() + " - MainCurrencyPrice=" + mainCurrencyPrice.ToString("#,#0.00") + " USD.");
|
||||
|
||||
if ((completedTicks % 100) == 0) {
|
||||
log.DoLogInfo("Poloniex - Our magicbots are still at work, hang on... " + completedTicks + "/" + totalTicks + " ticks done...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Poloniex - Initial market data created. Ready to go!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Core.ProfitTrailer {
|
||||
public static class SettingsAPI {
|
||||
public static List<string> GetPropertyLinesFromAPI(string ptFileName, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<string> result = null;
|
||||
|
||||
try {
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.DefaultConnectionLimit = 9999;
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(CertificateHelper.AllwaysGoodCertificate);
|
||||
|
||||
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(systemConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL + "settingsapi/settings/load");
|
||||
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
|
||||
httpWebRequest.Method = "POST";
|
||||
|
||||
// PT is using ordinary POST data, not JSON
|
||||
string query = "fileName=" + ptFileName + "&configName=" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "&license=" + systemConfiguration.GeneralSettings.Application.ProfitTrailerLicense;
|
||||
byte[] formData = Encoding.ASCII.GetBytes(query);
|
||||
httpWebRequest.ContentLength = formData.Length;
|
||||
|
||||
using (Stream stream = httpWebRequest.GetRequestStream()) {
|
||||
stream.Write(formData, 0, formData.Length);
|
||||
}
|
||||
|
||||
//using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream())) {
|
||||
// string json = JsonConvert.SerializeObject(new {
|
||||
// fileName = ptFileName,
|
||||
// configName = systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName,
|
||||
// license = systemConfiguration.GeneralSettings.Application.ProfitTrailerLicense
|
||||
// });
|
||||
|
||||
// streamWriter.Write(json);
|
||||
//}
|
||||
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
|
||||
using (StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream())) {
|
||||
string jsonResult = streamReader.ReadToEnd();
|
||||
result = JsonConvert.DeserializeObject<List<string>>(jsonResult);
|
||||
}
|
||||
} catch (WebException ex) {
|
||||
// Manual error handling as PT doesn't seem to provide a proper error response...
|
||||
if (ex.Message.IndexOf("401") > -1) {
|
||||
log.DoLogError("Loading " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': Unauthorized! The specified Profit Trailer license key '" + systemConfiguration.GetProfitTrailerLicenseKeyMasked() + "' is invalid!");
|
||||
} else {
|
||||
log.DoLogCritical("Loading " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': " + ex.Message, ex);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Loading " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': " + ex.Message, ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SendPropertyLinesToAPI(string ptFileName, List<string> lines, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
int retryCount = 0;
|
||||
int maxRetries = 3;
|
||||
bool transferCompleted = false;
|
||||
bool transferCanceled = false;
|
||||
|
||||
while (!transferCompleted && !transferCanceled) {
|
||||
try {
|
||||
ServicePointManager.Expect100Continue = true;
|
||||
ServicePointManager.DefaultConnectionLimit = 9999;
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(CertificateHelper.AllwaysGoodCertificate);
|
||||
|
||||
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(systemConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL + "settingsapi/settings/save");
|
||||
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
|
||||
httpWebRequest.Method = "POST";
|
||||
httpWebRequest.Proxy = null;
|
||||
httpWebRequest.Timeout = 30000;
|
||||
|
||||
// PT is using ordinary POST data, not JSON
|
||||
string query = "fileName=" + ptFileName + "&configName=" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "&license=" + systemConfiguration.GeneralSettings.Application.ProfitTrailerLicense;
|
||||
string propertiesString = SystemHelper.ConvertListToTokenString(lines, Environment.NewLine, false);
|
||||
query += "&saveData=" + WebUtility.UrlEncode(propertiesString);
|
||||
|
||||
byte[] formData = Encoding.ASCII.GetBytes(query);
|
||||
httpWebRequest.ContentLength = formData.Length;
|
||||
|
||||
using (Stream stream = httpWebRequest.GetRequestStream()) {
|
||||
stream.Write(formData, 0, formData.Length);
|
||||
}
|
||||
log.DoLogDebug("Built POST request for " + ptFileName + ".properties.");
|
||||
|
||||
//using (StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream())) {
|
||||
// string json = JsonConvert.SerializeObject(new {
|
||||
// fileName = ptFileName,
|
||||
// configName = systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName,
|
||||
// license = systemConfiguration.GeneralSettings.Application.ProfitTrailerLicense,
|
||||
// saveData = propertiesString
|
||||
// });
|
||||
|
||||
// streamWriter.Write(json);
|
||||
//}
|
||||
|
||||
log.DoLogInfo("Sending " + ptFileName + ".properties...");
|
||||
HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
|
||||
log.DoLogInfo(ptFileName + ".properties sent!");
|
||||
httpResponse.Close();
|
||||
log.DoLogDebug(ptFileName + ".properties response object closed.");
|
||||
transferCompleted = true;
|
||||
|
||||
} catch (WebException ex) {
|
||||
// Manual error handling as PT doesn't seem to provide a proper error response...
|
||||
if (ex.Message.IndexOf("401") > -1) {
|
||||
log.DoLogError("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': Unauthorized! The specified Profit Trailer license key '" + systemConfiguration.GetProfitTrailerLicenseKeyMasked() + "' is invalid!");
|
||||
transferCanceled = true;
|
||||
} else if (ex.Message.IndexOf("timed out") > -1) {
|
||||
// Handle timeout seperately
|
||||
retryCount++;
|
||||
if (retryCount <= maxRetries) {
|
||||
log.DoLogError("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': Timeout! Starting retry number " + retryCount + "/" + maxRetries.ToString() + "!");
|
||||
} else {
|
||||
transferCanceled = true;
|
||||
log.DoLogError("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': Timeout! Canceling transfer after " + maxRetries.ToString() + " failed retries.");
|
||||
}
|
||||
} else {
|
||||
log.DoLogCritical("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': " + ex.Message, ex);
|
||||
transferCanceled = true;
|
||||
}
|
||||
|
||||
} catch (TimeoutException ex) {
|
||||
retryCount++;
|
||||
if (retryCount <= maxRetries) {
|
||||
log.DoLogError("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': Timeout (" + ex.Message + ")! Starting retry number " + retryCount + "/" + maxRetries.ToString() + "!");
|
||||
} else {
|
||||
transferCanceled = true;
|
||||
log.DoLogError("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': Timeout (" + ex.Message + ")! Canceling transfer after " + maxRetries.ToString() + " failed retries.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Saving " + ptFileName + ".properties failed for setting '" + systemConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName + "': " + ex.Message, ex);
|
||||
transferCanceled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Core.ProfitTrailer {
|
||||
public static class SettingsFiles {
|
||||
public static string GetActiveSetting(PTMagicConfiguration systemConfiguration, string pairsFileName, string dcaFileName, string indicatorsFileName, LogHelper log) {
|
||||
string pairsPropertiesPath = systemConfiguration.GeneralSettings.Application.ProfitTrailerPath + Constants.PTPathTrading + Path.DirectorySeparatorChar + pairsFileName;
|
||||
|
||||
string result = SettingsFiles.GetActiveSettingFromFile(pairsPropertiesPath, systemConfiguration, log);
|
||||
|
||||
if (result.Equals("")) {
|
||||
SettingsFiles.WriteHeaderLines(pairsPropertiesPath, "Default", systemConfiguration);
|
||||
|
||||
string dcaPropertiesPath = systemConfiguration.GeneralSettings.Application.ProfitTrailerPath + Constants.PTPathTrading + Path.DirectorySeparatorChar + dcaFileName;
|
||||
SettingsFiles.WriteHeaderLines(dcaPropertiesPath, "Default", systemConfiguration);
|
||||
|
||||
string inditactorsPropertiesPath = systemConfiguration.GeneralSettings.Application.ProfitTrailerPath + Constants.PTPathTrading + Path.DirectorySeparatorChar + indicatorsFileName;
|
||||
SettingsFiles.WriteHeaderLines(inditactorsPropertiesPath, "Default", systemConfiguration);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void WriteHeaderLines(string filePath, string settingName, PTMagicConfiguration systemConfiguration) {
|
||||
// Writing Header lines
|
||||
List<string> lines = File.ReadAllLines(filePath).ToList();
|
||||
lines.Insert(0, "");
|
||||
lines.Insert(0, "# ####################################");
|
||||
lines.Insert(0, "# PTMagic_LastChanged = " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());
|
||||
lines.Insert(0, "# PTMagic_ActiveSetting = " + SystemHelper.StripBadCode(settingName, Constants.WhiteListProperties));
|
||||
lines.Insert(0, "# ####### PTMagic Current Setting ########");
|
||||
lines.Insert(0, "# ####################################");
|
||||
|
||||
if (!systemConfiguration.GeneralSettings.Application.TestMode) File.WriteAllLines(filePath, lines);
|
||||
}
|
||||
|
||||
public static string GetActiveSettingFromFile(string filePath, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
string result = "";
|
||||
|
||||
if (File.Exists(filePath)) {
|
||||
StreamReader sr = new StreamReader(filePath);
|
||||
try {
|
||||
string line = sr.ReadLine();
|
||||
while (line != null) {
|
||||
if (line.IndexOf("PTMagic_ActiveSetting", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
result = line.Replace("PTMagic_ActiveSetting", "", StringComparison.InvariantCultureIgnoreCase);
|
||||
result = result.Replace("#", "");
|
||||
result = result.Replace("=", "").Trim();
|
||||
result = SystemHelper.StripBadCode(result, Constants.WhiteListProperties);
|
||||
break;
|
||||
}
|
||||
line = sr.ReadLine();
|
||||
}
|
||||
} catch { } finally {
|
||||
sr.Close();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static List<string> GetPresetFileLinesAsList(string settingName, string fileName, PTMagicConfiguration systemConfiguration) {
|
||||
return SettingsFiles.GetPresetFileLinesAsList(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar, settingName, fileName, systemConfiguration);
|
||||
}
|
||||
|
||||
public static List<string> GetPresetFileLinesAsList(string baseFolderPath, string settingName, string fileName, PTMagicConfiguration systemConfiguration) {
|
||||
fileName = fileName.Replace(".PROPERTIES", ".properties");
|
||||
string settingPropertiesPath = baseFolderPath + Constants.PTMagicPathPresets + Path.DirectorySeparatorChar + settingName + Path.DirectorySeparatorChar + fileName;
|
||||
|
||||
List<string> result = new List<string>();
|
||||
if (File.Exists(settingPropertiesPath)) {
|
||||
result = File.ReadAllLines(settingPropertiesPath).ToList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool CheckPresets(PTMagicConfiguration systemConfiguration, LogHelper log, bool forceCheck) {
|
||||
if (!forceCheck) {
|
||||
|
||||
// If the check is not enforced, check for file changes
|
||||
string[] presetFilePaths = Directory.GetFiles(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathPresets, "*.*", SearchOption.AllDirectories);
|
||||
foreach (string presetFilePath in presetFilePaths) {
|
||||
if (presetFilePath.IndexOf(".properties", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
FileInfo presetFile = new FileInfo(presetFilePath);
|
||||
if (presetFile.LastWriteTime > DateTime.Now.AddMinutes(-systemConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes).AddSeconds(2)) {
|
||||
|
||||
// File has changed recently, force preparation check
|
||||
log.DoLogInfo("Preset files changed, enforcing preparation check...");
|
||||
forceCheck = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (forceCheck) {
|
||||
log.DoLogInfo("Checking automated settings for presets...");
|
||||
foreach (GlobalSetting setting in systemConfiguration.AnalyzerSettings.GlobalSettings) {
|
||||
if (setting.PairsProperties != null) {
|
||||
if (setting.PairsProperties.ContainsKey("File")) {
|
||||
setting.PairsProperties["File"] = SystemHelper.PropertyToString(setting.PairsProperties["File"]).Replace(".PROPERTIES", ".properties");
|
||||
|
||||
// Check preset PAIRS.PROPERTIES for header lines
|
||||
string settingPairsPropertiesPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathPresets + Path.DirectorySeparatorChar + setting.SettingName + Path.DirectorySeparatorChar + setting.PairsProperties["File"];
|
||||
string headerPairsSetting = SettingsFiles.GetActiveSettingFromFile(settingPairsPropertiesPath, systemConfiguration, log);
|
||||
if (headerPairsSetting.Equals("")) {
|
||||
if (File.Exists(settingPairsPropertiesPath)) {
|
||||
SettingsFiles.WriteHeaderLines(settingPairsPropertiesPath, setting.SettingName, systemConfiguration);
|
||||
} else {
|
||||
Exception ex = new Exception("Not able to find preset file " + SystemHelper.PropertyToString(setting.PairsProperties["File"]) + " for '" + setting.SettingName + "'");
|
||||
log.DoLogCritical("Not able to find preset file " + SystemHelper.PropertyToString(setting.PairsProperties["File"]) + " for '" + setting.SettingName + "'", ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.DoLogInfo("Prepared " + SystemHelper.PropertyToString(setting.PairsProperties["File"]) + " for '" + setting.SettingName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (setting.DCAProperties != null) {
|
||||
if (setting.DCAProperties.ContainsKey("File")) {
|
||||
setting.DCAProperties["File"] = SystemHelper.PropertyToString(setting.DCAProperties["File"]).Replace(".PROPERTIES", ".properties");
|
||||
|
||||
// Check preset DCA.PROPERTIES for header lines
|
||||
string settingDCAPropertiesPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathPresets + Path.DirectorySeparatorChar + setting.SettingName + Path.DirectorySeparatorChar + setting.DCAProperties["File"];
|
||||
string headerDCASetting = SettingsFiles.GetActiveSettingFromFile(settingDCAPropertiesPath, systemConfiguration, log);
|
||||
if (headerDCASetting.Equals("")) {
|
||||
if (File.Exists(settingDCAPropertiesPath)) {
|
||||
SettingsFiles.WriteHeaderLines(settingDCAPropertiesPath, setting.SettingName, systemConfiguration);
|
||||
} else {
|
||||
Exception ex = new Exception("Not able to find preset file " + SystemHelper.PropertyToString(setting.DCAProperties["File"]) + " for '" + setting.SettingName + "'");
|
||||
log.DoLogCritical("Not able to find preset file " + SystemHelper.PropertyToString(setting.DCAProperties["File"]) + " for '" + setting.SettingName + "'", ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Prepared " + SystemHelper.PropertyToString(setting.DCAProperties["File"]) + " for '" + setting.SettingName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (setting.IndicatorsProperties != null) {
|
||||
if (setting.IndicatorsProperties.ContainsKey("File")) {
|
||||
setting.IndicatorsProperties["File"] = SystemHelper.PropertyToString(setting.IndicatorsProperties["File"]).Replace(".PROPERTIES", ".properties");
|
||||
|
||||
// Check preset INDICATORS.PROPERTIES for header lines
|
||||
string settingIndicatorsPropertiesPath = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + Constants.PTMagicPathPresets + Path.DirectorySeparatorChar + setting.SettingName + Path.DirectorySeparatorChar + setting.IndicatorsProperties["File"];
|
||||
string headerIndicatorsSetting = SettingsFiles.GetActiveSettingFromFile(settingIndicatorsPropertiesPath, systemConfiguration, log);
|
||||
if (headerIndicatorsSetting.Equals("")) {
|
||||
if (File.Exists(settingIndicatorsPropertiesPath)) {
|
||||
SettingsFiles.WriteHeaderLines(settingIndicatorsPropertiesPath, setting.SettingName, systemConfiguration);
|
||||
} else {
|
||||
Exception ex = new Exception("Not able to find preset file " + SystemHelper.PropertyToString(setting.IndicatorsProperties["File"]) + " for '" + setting.SettingName + "'");
|
||||
log.DoLogCritical("Not able to find preset file " + SystemHelper.PropertyToString(setting.IndicatorsProperties["File"]) + " for '" + setting.SettingName + "'", ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Prepared " + SystemHelper.PropertyToString(setting.IndicatorsProperties["File"]) + " for '" + setting.SettingName + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return forceCheck;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,512 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Core.ProfitTrailer {
|
||||
public static class SettingsHandler {
|
||||
public static string GetMainMarket(PTMagicConfiguration systemConfiguration, List<string> pairsLines, LogHelper log) {
|
||||
string result = "";
|
||||
|
||||
foreach (string line in pairsLines) {
|
||||
if (line.Replace(" ", "").StartsWith("MARKET", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
result = line.Replace("MARKET", "", StringComparison.InvariantCultureIgnoreCase);
|
||||
result = result.Replace("#", "");
|
||||
result = result.Replace("=", "").Trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetMarketPairs(PTMagicConfiguration systemConfiguration, List<string> pairsLines, LogHelper log) {
|
||||
string result = "";
|
||||
|
||||
foreach (string line in pairsLines) {
|
||||
if (line.Replace(" ", "").StartsWith("ALL_enabled_pairs", StringComparison.InvariantCultureIgnoreCase) || line.Replace(" ", "").StartsWith("enabled_pairs", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
result = line.Replace("ALL_enabled_pairs", "", StringComparison.InvariantCultureIgnoreCase);
|
||||
result = result.Replace("enabled_pairs", "", StringComparison.InvariantCultureIgnoreCase);
|
||||
result = result.Replace("#", "");
|
||||
result = result.Replace("=", "").Trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetActiveSetting(PTMagicConfiguration systemConfiguration, string defaultSettingName, ref bool headerLinesAdded, List<string> pairsLines, List<string> dcaLines, List<string> indicatorsLines, LogHelper log) {
|
||||
string result = "";
|
||||
|
||||
foreach (string line in pairsLines) {
|
||||
if (line.IndexOf("PTMagic_ActiveSetting", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
result = line.Replace("PTMagic_ActiveSetting", "", StringComparison.InvariantCultureIgnoreCase);
|
||||
result = result.Replace("#", "");
|
||||
result = result.Replace("=", "").Trim();
|
||||
result = SystemHelper.StripBadCode(result, Constants.WhiteListProperties);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Equals("")) {
|
||||
SettingsHandler.WriteHeaderLines(ref pairsLines, defaultSettingName, systemConfiguration);
|
||||
SettingsHandler.WriteHeaderLines(ref dcaLines, defaultSettingName, systemConfiguration);
|
||||
SettingsHandler.WriteHeaderLines(ref indicatorsLines, defaultSettingName, systemConfiguration);
|
||||
headerLinesAdded = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void WriteHeaderLines(ref List<string> lines, string settingName, PTMagicConfiguration systemConfiguration) {
|
||||
// Writing Header lines
|
||||
lines.Insert(0, "");
|
||||
lines.Insert(0, "# ####################################");
|
||||
lines.Insert(0, "# PTMagic_LastChanged = " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());
|
||||
lines.Insert(0, "# PTMagic_ActiveSetting = " + SystemHelper.StripBadCode(settingName, Constants.WhiteListProperties));
|
||||
lines.Insert(0, "# ####### PTMagic Current Setting ########");
|
||||
lines.Insert(0, "# ####################################");
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> GetPropertiesAsDictionary(List<string> propertyLines) {
|
||||
Dictionary<string, string> result = new Dictionary<string, string>();
|
||||
|
||||
foreach (string line in propertyLines) {
|
||||
if (!line.StartsWith("#", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
string[] lineContentArray = line.Split("=");
|
||||
if (lineContentArray.Length == 2) {
|
||||
if (!result.ContainsKey(lineContentArray[0].Trim())) {
|
||||
result.Add(lineContentArray[0].Trim(), lineContentArray[1].Trim());
|
||||
} else {
|
||||
result[lineContentArray[0].Trim()] = lineContentArray[1].Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetCurrentPropertyValue(Dictionary<string, string> properties, string propertyKey, string fallbackPropertyKey) {
|
||||
string result = "";
|
||||
|
||||
if (properties.ContainsKey(propertyKey)) {
|
||||
result = properties[propertyKey];
|
||||
} else if (!fallbackPropertyKey.Equals("") && properties.ContainsKey(fallbackPropertyKey)) {
|
||||
result = properties[fallbackPropertyKey];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CompileProperties(string defaultSettingName, GlobalSetting setting, PTMagicConfiguration systemConfiguration, List<string> pairsLines, List<string> dcaLines, List<string> indicatorsLines, LogHelper log) {
|
||||
SettingsHandler.BuildPropertyLines(ref pairsLines, defaultSettingName, setting, setting.PairsProperties, "pairs", systemConfiguration, log);
|
||||
SettingsHandler.BuildPropertyLines(ref dcaLines, defaultSettingName, setting, setting.DCAProperties, "dca", systemConfiguration, log);
|
||||
SettingsHandler.BuildPropertyLines(ref indicatorsLines, defaultSettingName, setting, setting.IndicatorsProperties, "indicators", systemConfiguration, log);
|
||||
}
|
||||
|
||||
public static void BuildPropertyLines(ref List<string> lines, string defaultSettingName, GlobalSetting setting, Dictionary<string, object> properties, string propertiesType, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
List<string> result = new List<string>();
|
||||
|
||||
if (properties != null) {
|
||||
|
||||
// Building Properties
|
||||
if (!setting.SettingName.Equals(defaultSettingName, StringComparison.InvariantCultureIgnoreCase) && systemConfiguration.GeneralSettings.Application.AlwaysLoadDefaultBeforeSwitch && !properties.ContainsKey("File")) {
|
||||
|
||||
// Load default settings as basis for the switch
|
||||
GlobalSetting defaultSetting = systemConfiguration.AnalyzerSettings.GlobalSettings.Find(a => a.SettingName.Equals(defaultSettingName, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (defaultSetting != null) {
|
||||
|
||||
Dictionary<string, object> defaultProperties = new Dictionary<string, object>();
|
||||
switch (propertiesType) {
|
||||
case "pairs":
|
||||
defaultProperties = defaultSetting.PairsProperties;
|
||||
break;
|
||||
case "dca":
|
||||
defaultProperties = defaultSetting.DCAProperties;
|
||||
break;
|
||||
case "inidcators":
|
||||
defaultProperties = defaultSetting.IndicatorsProperties;
|
||||
break;
|
||||
}
|
||||
|
||||
if (defaultProperties.ContainsKey("File")) {
|
||||
lines = SettingsFiles.GetPresetFileLinesAsList(defaultSetting.SettingName, defaultProperties["File"].ToString(), systemConfiguration);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Check if settings are configured in a seperate file
|
||||
if (properties.ContainsKey("File")) {
|
||||
lines = SettingsFiles.GetPresetFileLinesAsList(setting.SettingName, properties["File"].ToString(), systemConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string line in lines) {
|
||||
if (line.IndexOf("PTMagic_ActiveSetting", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Setting current active setting
|
||||
result.Add("# PTMagic_ActiveSetting = " + setting.SettingName);
|
||||
|
||||
} else if (line.IndexOf("PTMagic_LastChanged", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Setting last change datetime
|
||||
result.Add("# PTMagic_LastChanged = " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString());
|
||||
|
||||
} else if (line.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
break;
|
||||
} else {
|
||||
|
||||
// Writing property items
|
||||
int oldResultCount = result.Count;
|
||||
if (properties != null) {
|
||||
foreach (string settingProperty in properties.Keys) {
|
||||
result = SettingsHandler.BuildPropertyLine(result, setting.SettingName, line, properties, settingProperty);
|
||||
}
|
||||
}
|
||||
if (oldResultCount == result.Count) result.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines = result;
|
||||
}
|
||||
|
||||
public static List<string> BuildPropertyLine(List<string> result, string settingName, string line, Dictionary<string, object> properties, string settingProperty) {
|
||||
int valueMode = Constants.ValueModeDefault;
|
||||
string propertyKey = settingProperty;
|
||||
|
||||
// Check for offset values
|
||||
if (propertyKey.IndexOf("_OFFSETPERCENT") > -1) {
|
||||
valueMode = Constants.ValueModeOffsetPercent;
|
||||
propertyKey = propertyKey.Replace("_OFFSETPERCENT", "");
|
||||
} else if (propertyKey.IndexOf("_OFFSET") > -1) {
|
||||
valueMode = Constants.ValueModeOffset;
|
||||
propertyKey = propertyKey.Replace("_OFFSET", "");
|
||||
}
|
||||
|
||||
if (line.StartsWith(propertyKey + " ", StringComparison.InvariantCultureIgnoreCase) || line.StartsWith(propertyKey + "=", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
string newValueString = SystemHelper.PropertyToString(properties[settingProperty]);
|
||||
if (newValueString.ToLower().Equals("true") || newValueString.ToLower().Equals("false")) {
|
||||
newValueString = newValueString.ToLower();
|
||||
}
|
||||
|
||||
string oldValueString = line.Replace(propertyKey, "").Replace("=", "").Trim();
|
||||
|
||||
switch (valueMode) {
|
||||
case Constants.ValueModeOffset:
|
||||
// Offset value by a fixed amount
|
||||
double offsetValue = SystemHelper.TextToDouble(newValueString, 0, "en-US");
|
||||
if (offsetValue != 0) {
|
||||
double oldValue = SystemHelper.TextToDouble(oldValueString, 0, "en-US");
|
||||
newValueString = Math.Round((oldValue + offsetValue), 8).ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
break;
|
||||
case Constants.ValueModeOffsetPercent:
|
||||
// Offset value by percentage
|
||||
double offsetValuePercent = SystemHelper.TextToDouble(newValueString, 0, "en-US");
|
||||
if (offsetValuePercent != 0) {
|
||||
double oldValue = SystemHelper.TextToDouble(oldValueString, 0, "en-US");
|
||||
if (oldValue < 0) offsetValuePercent = offsetValuePercent * -1;
|
||||
double oldValueOffset = (oldValue * (offsetValuePercent / 100));
|
||||
newValueString = Math.Round((oldValue + oldValueOffset), 8).ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
line = propertyKey + " = " + newValueString;
|
||||
|
||||
string previousLine = result.Last();
|
||||
if (previousLine.IndexOf("PTMagic Changed Line", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
previousLine = "# PTMagic changed line for setting '" + settingName + "' on " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString();
|
||||
|
||||
result.RemoveAt(result.Count - 1);
|
||||
result.Add(previousLine);
|
||||
} else {
|
||||
string editLine = "# PTMagic changed line for setting '" + settingName + "' on " + DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString();
|
||||
result.Add(editLine);
|
||||
}
|
||||
result.Add(line);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void CompileSingleMarketProperties(int ptMajorVersion, string mainMarket, Dictionary<string, List<SingleMarketSetting>> singleMarketSettings, Dictionary<string, List<string>> matchedTriggers, PTMagicConfiguration systemConfiguration, List<string> pairsLines, List<string> dcaLines, List<string> indicatorsLines, LogHelper log) {
|
||||
try {
|
||||
List<string> globalPairsLines = new List<string>();
|
||||
List<string> globalDCALines = new List<string>();
|
||||
List<string> globalIndicatorsLines = new List<string>();
|
||||
|
||||
List<string> newPairsLines = new List<string>();
|
||||
List<string> newDCALines = new List<string>();
|
||||
List<string> newIndicatorsLines = new List<string>();
|
||||
|
||||
foreach (string pairsLine in pairsLines) {
|
||||
if (pairsLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
break;
|
||||
} else {
|
||||
string globalPairsLine = pairsLine;
|
||||
|
||||
globalPairsLines.Add(globalPairsLine);
|
||||
}
|
||||
}
|
||||
|
||||
newPairsLines.Add("# PTMagic_SingleMarketSettings - Written on " + DateTime.Now.ToString());
|
||||
newPairsLines.Add("# ########################################################################");
|
||||
newPairsLines.Add("");
|
||||
|
||||
foreach (string dcaLine in dcaLines) {
|
||||
if (dcaLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
break;
|
||||
} else {
|
||||
string globalDCALine = dcaLine;
|
||||
|
||||
globalDCALines.Add(globalDCALine);
|
||||
}
|
||||
}
|
||||
|
||||
newDCALines.Add("# PTMagic_SingleMarketSettings - Written on " + DateTime.Now.ToString());
|
||||
newDCALines.Add("# ########################################################################");
|
||||
newDCALines.Add("");
|
||||
|
||||
foreach (string indicatorsLine in indicatorsLines) {
|
||||
if (indicatorsLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
break;
|
||||
} else {
|
||||
string globalIndicatorsLine = indicatorsLine;
|
||||
|
||||
globalIndicatorsLines.Add(globalIndicatorsLine);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, string> globalPairsProperties = SettingsHandler.GetPropertiesAsDictionary(globalPairsLines);
|
||||
Dictionary<string, string> globalDCAProperties = SettingsHandler.GetPropertiesAsDictionary(globalDCALines);
|
||||
Dictionary<string, string> globalIndicatorsProperties = SettingsHandler.GetPropertiesAsDictionary(globalIndicatorsLines);
|
||||
|
||||
newIndicatorsLines.Add("# PTMagic_SingleMarketSettings - Written on " + DateTime.Now.ToString());
|
||||
newIndicatorsLines.Add("# ########################################################################");
|
||||
newIndicatorsLines.Add("");
|
||||
|
||||
foreach (string marketPair in singleMarketSettings.Keys.OrderBy(k => k)) {
|
||||
Dictionary<string, object> pairsPropertiesToApply = new Dictionary<string, object>();
|
||||
Dictionary<string, object> dcaPropertiesToApply = new Dictionary<string, object>();
|
||||
Dictionary<string, object> indicatorsPropertiesToApply = new Dictionary<string, object>();
|
||||
|
||||
// Build Properties as a whole list so that a single coin also has only one block with single market settings applied to it
|
||||
foreach (SingleMarketSetting setting in singleMarketSettings[marketPair]) {
|
||||
log.DoLogInfo("Building single market settings '" + setting.SettingName + "' for '" + marketPair + "'...");
|
||||
|
||||
foreach (string settingPairsProperty in setting.PairsProperties.Keys) {
|
||||
if (!pairsPropertiesToApply.ContainsKey(settingPairsProperty)) {
|
||||
pairsPropertiesToApply.Add(settingPairsProperty, setting.PairsProperties[settingPairsProperty]);
|
||||
} else {
|
||||
pairsPropertiesToApply[settingPairsProperty] = setting.PairsProperties[settingPairsProperty];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string settingDCAProperty in setting.DCAProperties.Keys) {
|
||||
if (!dcaPropertiesToApply.ContainsKey(settingDCAProperty)) {
|
||||
dcaPropertiesToApply.Add(settingDCAProperty, setting.DCAProperties[settingDCAProperty]);
|
||||
} else {
|
||||
dcaPropertiesToApply[settingDCAProperty] = setting.DCAProperties[settingDCAProperty];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string settingIndicatorsProperty in setting.IndicatorsProperties.Keys) {
|
||||
if (!indicatorsPropertiesToApply.ContainsKey(settingIndicatorsProperty)) {
|
||||
indicatorsPropertiesToApply.Add(settingIndicatorsProperty, setting.IndicatorsProperties[settingIndicatorsProperty]);
|
||||
} else {
|
||||
indicatorsPropertiesToApply[settingIndicatorsProperty] = setting.IndicatorsProperties[settingIndicatorsProperty];
|
||||
}
|
||||
}
|
||||
|
||||
log.DoLogInfo("Built single market settings '" + setting.SettingName + "' for '" + marketPair + "'.");
|
||||
}
|
||||
|
||||
newPairsLines = SettingsHandler.BuildPropertyLinesForSingleMarketSetting(ptMajorVersion, mainMarket, marketPair, singleMarketSettings[marketPair], pairsPropertiesToApply, matchedTriggers, globalPairsProperties, newPairsLines, systemConfiguration, log);
|
||||
newDCALines = SettingsHandler.BuildPropertyLinesForSingleMarketSetting(ptMajorVersion, mainMarket, marketPair, singleMarketSettings[marketPair], dcaPropertiesToApply, matchedTriggers, globalDCAProperties, newDCALines, systemConfiguration, log);
|
||||
newIndicatorsLines = SettingsHandler.BuildPropertyLinesForSingleMarketSetting(ptMajorVersion, mainMarket, marketPair, singleMarketSettings[marketPair], indicatorsPropertiesToApply, matchedTriggers, globalIndicatorsProperties, newIndicatorsLines, systemConfiguration, log);
|
||||
}
|
||||
|
||||
// Combine global settings lines with single market settings lines
|
||||
globalPairsLines.AddRange(newPairsLines);
|
||||
globalDCALines.AddRange(newDCALines);
|
||||
globalIndicatorsLines.AddRange(newIndicatorsLines);
|
||||
|
||||
pairsLines = globalPairsLines;
|
||||
dcaLines = globalDCALines;
|
||||
indicatorsLines = globalIndicatorsLines;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Critical error while writing settings!", ex);
|
||||
throw (ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> BuildPropertyLinesForSingleMarketSetting(int ptMajorVersion, string mainMarket, string marketPair, List<SingleMarketSetting> appliedSettings, Dictionary<string, object> properties, Dictionary<string, List<string>> matchedTriggers, Dictionary<string, string> fullProperties, List<string> newPropertyLines, PTMagicConfiguration systemConfiguration, LogHelper log) {
|
||||
if (properties.Keys.Count > 0) {
|
||||
string appliedSettingsStringList = "";
|
||||
foreach (SingleMarketSetting sms in appliedSettings) {
|
||||
if (!appliedSettingsStringList.Equals("")) appliedSettingsStringList += ", ";
|
||||
appliedSettingsStringList += sms.SettingName;
|
||||
}
|
||||
|
||||
newPropertyLines.Add("# " + marketPair + " - Current active settings: " + appliedSettingsStringList);
|
||||
newPropertyLines.Add("# Matching triggers:");
|
||||
foreach (string matchingTrigger in matchedTriggers[marketPair]) {
|
||||
newPropertyLines.Add("# " + matchingTrigger);
|
||||
}
|
||||
|
||||
foreach (string settingProperty in properties.Keys) {
|
||||
int valueMode = Constants.ValueModeDefault;
|
||||
string propertyKey = settingProperty;
|
||||
|
||||
// Check for offset values
|
||||
if (propertyKey.IndexOf("_OFFSETPERCENT") > -1) {
|
||||
valueMode = Constants.ValueModeOffsetPercent;
|
||||
propertyKey = propertyKey.Replace("_OFFSETPERCENT", "");
|
||||
} else if (propertyKey.IndexOf("_OFFSET") > -1) {
|
||||
valueMode = Constants.ValueModeOffset;
|
||||
propertyKey = propertyKey.Replace("_OFFSET", "");
|
||||
}
|
||||
|
||||
string newValueString = SystemHelper.PropertyToString(properties[settingProperty]);
|
||||
if (newValueString.ToLower().Equals("true") || newValueString.ToLower().Equals("false")) {
|
||||
newValueString = newValueString.ToLower();
|
||||
}
|
||||
|
||||
string propertyMarketName = marketPair;
|
||||
if (ptMajorVersion > 1) {
|
||||
// Adjust market pair name for PT 2.0 and above
|
||||
propertyMarketName = propertyMarketName.Replace(mainMarket, "").Replace("_", "").Replace("-", "");
|
||||
}
|
||||
|
||||
string propertyKeyString = "";
|
||||
if (propertyKey.StartsWith("ALL", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
propertyKeyString = propertyKey.Replace("ALL", propertyMarketName, StringComparison.InvariantCultureIgnoreCase);
|
||||
} else if (propertyKey.StartsWith("DEFAULT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
propertyKeyString = propertyKey.Replace("DEFAULT", propertyMarketName, StringComparison.InvariantCultureIgnoreCase);
|
||||
} else {
|
||||
if (propertyKey.StartsWith("_", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
propertyKeyString = propertyMarketName + propertyKey;
|
||||
} else {
|
||||
propertyKeyString = propertyMarketName + "_" + propertyKey;
|
||||
}
|
||||
}
|
||||
|
||||
switch (valueMode) {
|
||||
case Constants.ValueModeOffset:
|
||||
// Offset value by a fixed amount
|
||||
double offsetValue = SystemHelper.TextToDouble(newValueString, 0, "en-US");
|
||||
if (offsetValue != 0) {
|
||||
double oldValue = SystemHelper.TextToDouble(SettingsHandler.GetCurrentPropertyValue(fullProperties, propertyKey, propertyKey.Replace("ALL_", "DEFAULT_")), 0, "en-US");
|
||||
newValueString = Math.Round((oldValue + offsetValue), 8).ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
break;
|
||||
case Constants.ValueModeOffsetPercent:
|
||||
// Offset value by percentage
|
||||
double offsetValuePercent = SystemHelper.TextToDouble(newValueString, 0, "en-US");
|
||||
if (offsetValuePercent != 0) {
|
||||
double oldValue = SystemHelper.TextToDouble(SettingsHandler.GetCurrentPropertyValue(fullProperties, propertyKey, propertyKey.Replace("ALL_", "DEFAULT_")), 0, "en-US");
|
||||
if (oldValue < 0) offsetValuePercent = offsetValuePercent * -1;
|
||||
double oldValueOffset = (oldValue * (offsetValuePercent / 100));
|
||||
newValueString = Math.Round((oldValue + oldValueOffset), 8).ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
newPropertyLines.Add(propertyKeyString + " = " + newValueString);
|
||||
}
|
||||
newPropertyLines.Add("");
|
||||
}
|
||||
|
||||
return newPropertyLines;
|
||||
}
|
||||
|
||||
public static bool RemoveSingleMarketSettings(PTMagicConfiguration systemConfiguration, List<string> pairsLines, List<string> dcaLines, List<string> indicatorsLines, LogHelper log) {
|
||||
bool result = false;
|
||||
try {
|
||||
List<string> cleanedUpPairsLines = new List<string>();
|
||||
List<string> cleanedUpDCALines = new List<string>();
|
||||
List<string> cleanedUpIndicatorsLines = new List<string>();
|
||||
|
||||
bool removedPairsSingleMarketSettings = false;
|
||||
foreach (string pairsLine in pairsLines) {
|
||||
if (pairsLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
removedPairsSingleMarketSettings = true;
|
||||
break;
|
||||
} else {
|
||||
string newPairsLine = pairsLine;
|
||||
|
||||
cleanedUpPairsLines.Add(newPairsLine);
|
||||
}
|
||||
}
|
||||
|
||||
bool removedDCASingleMarketSettings = false;
|
||||
foreach (string dcaLine in dcaLines) {
|
||||
if (dcaLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
removedDCASingleMarketSettings = true;
|
||||
break;
|
||||
} else {
|
||||
string newDCALine = dcaLine;
|
||||
|
||||
cleanedUpDCALines.Add(newDCALine);
|
||||
}
|
||||
}
|
||||
|
||||
bool removedIndicatorsSingleMarketSettings = false;
|
||||
foreach (string indicatorsLine in indicatorsLines) {
|
||||
if (indicatorsLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
|
||||
// Single Market Settings will get overwritten every single run => crop the lines
|
||||
removedIndicatorsSingleMarketSettings = true;
|
||||
break;
|
||||
} else {
|
||||
string newIndicatorsLine = indicatorsLine;
|
||||
|
||||
cleanedUpIndicatorsLines.Add(newIndicatorsLine);
|
||||
}
|
||||
}
|
||||
|
||||
pairsLines = cleanedUpPairsLines;
|
||||
dcaLines = cleanedUpDCALines;
|
||||
indicatorsLines = cleanedUpIndicatorsLines;
|
||||
|
||||
result = removedPairsSingleMarketSettings && removedDCASingleMarketSettings && removedIndicatorsSingleMarketSettings;
|
||||
} catch (Exception ex) {
|
||||
log.DoLogCritical("Critical error while writing settings!", ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,334 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Core.ProfitTrailer {
|
||||
public static class StrategyHelper {
|
||||
public static string GetStrategyShortcut(string strategyName, bool onlyValidStrategies) {
|
||||
string result = strategyName;
|
||||
|
||||
switch (strategyName.ToLower()) {
|
||||
case "lowbb":
|
||||
result = "LBB";
|
||||
break;
|
||||
case "highbb":
|
||||
result = "HBB";
|
||||
break;
|
||||
case "gain":
|
||||
result = "G";
|
||||
break;
|
||||
case "loss":
|
||||
result = "L";
|
||||
break;
|
||||
case "smagain":
|
||||
result = "SG";
|
||||
break;
|
||||
case "emagain":
|
||||
result = "EG";
|
||||
break;
|
||||
case "smaspread":
|
||||
result = "SS";
|
||||
break;
|
||||
case "emaspread":
|
||||
result = "ES";
|
||||
break;
|
||||
case "smacross":
|
||||
result = "SC";
|
||||
break;
|
||||
case "emacross":
|
||||
result = "EC";
|
||||
break;
|
||||
case "rsi":
|
||||
result = "RSI";
|
||||
break;
|
||||
case "stoch":
|
||||
result = "STOCH";
|
||||
break;
|
||||
case "stochrsi":
|
||||
result = "SRSI";
|
||||
break;
|
||||
case "macd":
|
||||
result = "MACD";
|
||||
break;
|
||||
case "obv":
|
||||
result = "OBV";
|
||||
break;
|
||||
case "bbwidth":
|
||||
result = "BBW";
|
||||
break;
|
||||
case "anderson":
|
||||
result = "AND";
|
||||
break;
|
||||
case "som enabled":
|
||||
result = "SOM";
|
||||
break;
|
||||
case "max buy times":
|
||||
result = "MAX";
|
||||
break;
|
||||
case "max pairs":
|
||||
result = "PAIRS";
|
||||
break;
|
||||
case "max spread":
|
||||
result = "SPREAD";
|
||||
break;
|
||||
case "price increase":
|
||||
result = "PIN";
|
||||
break;
|
||||
case "min buy volume":
|
||||
result = "VOL";
|
||||
break;
|
||||
case "min buy balance":
|
||||
result = "MIN";
|
||||
break;
|
||||
case "coin age":
|
||||
result = "AGE";
|
||||
break;
|
||||
case "too new":
|
||||
result = "NEW";
|
||||
break;
|
||||
case "blacklisted":
|
||||
result = "BLACK";
|
||||
break;
|
||||
case "insufficient balance":
|
||||
result = "BAL";
|
||||
break;
|
||||
case "max cost reached":
|
||||
result = "COST";
|
||||
break;
|
||||
case "rebuy timeout":
|
||||
result = "TIMEOUT";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (onlyValidStrategies) {
|
||||
if (strategyName.IndexOf("SOM") > -1 || strategyName.IndexOf("MAX") > -1 || strategyName.IndexOf("MIN") > -1 || strategyName.IndexOf("PRICE") > -1 || strategyName.IndexOf("BLACK") > -1 || strategyName.IndexOf("INSUFFICIENT") > -1 || strategyName.IndexOf("COST") > -1 || strategyName.IndexOf("TIMEOUT") > -1) {
|
||||
result = "";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsValidStrategy(string strategyName) {
|
||||
return StrategyHelper.IsValidStrategy(strategyName, false);
|
||||
}
|
||||
|
||||
public static bool IsValidStrategy(string strategyName, bool checkForAnyInvalid) {
|
||||
bool result = false;
|
||||
|
||||
if (!checkForAnyInvalid) {
|
||||
switch (strategyName.ToLower()) {
|
||||
case "lowbb":
|
||||
case "highbb":
|
||||
case "gain":
|
||||
case "loss":
|
||||
case "smagain":
|
||||
case "emagain":
|
||||
case "smaspread":
|
||||
case "emaspread":
|
||||
case "smacross":
|
||||
case "emacross":
|
||||
case "rsi":
|
||||
case "stoch":
|
||||
case "stochrsi":
|
||||
case "macd":
|
||||
case "obv":
|
||||
case "bbwidth":
|
||||
case "anderson":
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (strategyName.IndexOf("max", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("min", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("som", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("price", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("black", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("new", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("insufficient", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("timeout", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("spread", StringComparison.InvariantCultureIgnoreCase) == -1
|
||||
&& strategyName.IndexOf("pairs", StringComparison.InvariantCultureIgnoreCase) == -1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int GetStrategyValueDecimals(string strategyName) {
|
||||
int result = 0;
|
||||
|
||||
switch (strategyName.ToLower()) {
|
||||
case "lowbb":
|
||||
case "highbb":
|
||||
result = 8;
|
||||
break;
|
||||
case "gain":
|
||||
case "loss":
|
||||
case "smagain":
|
||||
case "emagain":
|
||||
case "smaspread":
|
||||
case "emaspread":
|
||||
case "anderson":
|
||||
case "smacross":
|
||||
case "emacross":
|
||||
result = 2;
|
||||
break;
|
||||
case "rsi":
|
||||
case "stochrsi":
|
||||
case "stoch":
|
||||
case "macd":
|
||||
case "obv":
|
||||
case "bbwidth":
|
||||
result = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetStrategyText(Summary summary, List<Strategy> strategies, string strategyText, bool isTrailingBuyActive) {
|
||||
if (strategies.Count > 0) {
|
||||
foreach (Strategy strategy in strategies) {
|
||||
string textClass = (strategy.IsTrailing) ? "label-success" : "label-danger";
|
||||
if (!StrategyHelper.IsValidStrategy(strategy.Name)) {
|
||||
strategyText += "<span class=\"label label-warning\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"" + strategy.Name + "\">" + StrategyHelper.GetStrategyShortcut(strategy.Name, false) + "</span> ";
|
||||
} else {
|
||||
strategyText += "<span class=\"label " + textClass + "\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"" + strategy.Name + "\">" + StrategyHelper.GetStrategyShortcut(strategy.Name, false) + "</span> ";
|
||||
}
|
||||
}
|
||||
|
||||
if (isTrailingBuyActive) {
|
||||
strategyText += " <i class=\"fa fa-flag text-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Trailing active!\"></i>";
|
||||
}
|
||||
} else {
|
||||
if (isTrailingBuyActive) {
|
||||
strategyText = "<span class=\"label label-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"" + strategyText + "\">" + StrategyHelper.GetStrategyShortcut(strategyText, true) + "</span>";
|
||||
strategyText += " <i class=\"fa fa-flag text-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Trailing active!\"></i>";
|
||||
} else {
|
||||
if (StrategyHelper.IsValidStrategy(strategyText)) {
|
||||
strategyText = "<span class=\"label label-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"" + strategyText + "\">" + StrategyHelper.GetStrategyShortcut(strategyText, true) + "</span>";
|
||||
} else if (strategyText.Equals("")) {
|
||||
strategyText = summary.DCABuyStrategy;
|
||||
strategyText = "<span class=\"label label-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"" + strategyText + "\">" + StrategyHelper.GetStrategyShortcut(strategyText, true) + "</span>";
|
||||
} else {
|
||||
strategyText = "<span class=\"label label-warning\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"" + strategyText + "\">" + StrategyHelper.GetStrategyShortcut(strategyText, false) + "</span> ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strategyText;
|
||||
}
|
||||
|
||||
public static string GetCurrentValueText(List<Strategy> strategies, string strategyText, double bbValue, double simpleValue, bool includeShortcut) {
|
||||
string result = "";
|
||||
|
||||
if (strategies.Count > 0) {
|
||||
foreach (Strategy strategy in strategies) {
|
||||
if (StrategyHelper.IsValidStrategy(strategy.Name)) {
|
||||
if (!result.Equals("")) result += "<br />";
|
||||
|
||||
string decimalFormat = "";
|
||||
int decimals = StrategyHelper.GetStrategyValueDecimals(strategy.Name);
|
||||
for (int d = 1; d <= decimals; d++) {
|
||||
decimalFormat += "0";
|
||||
}
|
||||
|
||||
if (includeShortcut) {
|
||||
result += "<span class=\"text-muted\">" + StrategyHelper.GetStrategyShortcut(strategy.Name, true) + "</span> ";
|
||||
}
|
||||
|
||||
if (StrategyHelper.GetStrategyShortcut(strategy.Name, true).IndexOf("and", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
result += simpleValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
} else {
|
||||
if (decimals == 0) {
|
||||
if (!SystemHelper.IsInteger(strategy.CurrentValue)) {
|
||||
result += strategy.CurrentValue.ToString("#,#", new System.Globalization.CultureInfo("en-US"));
|
||||
} else {
|
||||
result += strategy.CurrentValue.ToString("#,#0", new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
} else {
|
||||
result += strategy.CurrentValue.ToString("#,#0." + decimalFormat, new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (StrategyHelper.GetStrategyShortcut(strategyText, true).IndexOf("bb", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
result = bbValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"));
|
||||
} else {
|
||||
result = simpleValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetTriggerValueText(Summary summary, List<Strategy> strategies, string strategyText, double bbValue, double simpleValue, int buyLevel, bool includeShortcut) {
|
||||
string result = "";
|
||||
|
||||
if (strategies.Count > 0) {
|
||||
foreach (Strategy strategy in strategies) {
|
||||
if (StrategyHelper.IsValidStrategy(strategy.Name)) {
|
||||
if (!result.Equals("")) result += "<br />";
|
||||
|
||||
string decimalFormat = "";
|
||||
int decimals = StrategyHelper.GetStrategyValueDecimals(strategy.Name);
|
||||
for (int d = 1; d <= decimals; d++) {
|
||||
decimalFormat += "0";
|
||||
}
|
||||
|
||||
if (includeShortcut) {
|
||||
result += "<span class=\"text-muted\">" + StrategyHelper.GetStrategyShortcut(strategy.Name, true) + "</span> ";
|
||||
}
|
||||
|
||||
if (StrategyHelper.GetStrategyShortcut(strategy.Name, true).IndexOf("and", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
result += strategy.TriggerValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
} else {
|
||||
if (decimals == 0) {
|
||||
if (!SystemHelper.IsInteger(strategy.EntryValue)) {
|
||||
result += strategy.EntryValue.ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
} else {
|
||||
result += strategy.EntryValue.ToString("#,#0", new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
} else {
|
||||
result += strategy.EntryValue.ToString("#,#0." + decimalFormat, new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (StrategyHelper.GetStrategyShortcut(strategyText, true).IndexOf("bb", StringComparison.InvariantCultureIgnoreCase) > -1) {
|
||||
result = bbValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"));
|
||||
} else {
|
||||
if (simpleValue == Constants.MinTrendChange) {
|
||||
if (summary.DCATriggers.ContainsKey(buyLevel + 1)) {
|
||||
simpleValue = summary.DCATriggers[buyLevel + 1];
|
||||
}
|
||||
}
|
||||
result = simpleValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<!--<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>-->
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="wwwroot\assets\**" />
|
||||
<Content Remove="wwwroot\assets\**" />
|
||||
<EmbeddedResource Remove="wwwroot\assets\**" />
|
||||
<None Remove="wwwroot\assets\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="wwwroot\assets\css\custom.css" CopyToOutputDirectory="Always" />
|
||||
<Content Include="wwwroot\assets\img\qr-btc.png" CopyToOutputDirectory="Always" />
|
||||
<Content Include="wwwroot\assets\img\qr-eth.png" CopyToOutputDirectory="Always" />
|
||||
<Content Include="wwwroot\assets\img\qr-ltc.png" CopyToOutputDirectory="Always" />
|
||||
<Content Include="wwwroot\assets\img\qr-neo.png" CopyToOutputDirectory="Always" />
|
||||
<Content Include="wwwroot\assets\js\custom.js" CopyToOutputDirectory="Always" />
|
||||
<Content Include="wwwroot\assets\js\analyzer-settings.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="wwwroot\assets\js\dca.js" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.1-beta3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ProjectExtensions><VisualStudio><UserProperties Properties_4launchSettings_1json__JSONSchema="http://json.schemastore.org/composer" /></VisualStudio></ProjectExtensions>
|
||||
</Project>
|
|
@ -0,0 +1,102 @@
|
|||
@page
|
||||
@model BagAnalyzerModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
|
||||
@if (Model.PTData.DCALog.Count == 0) {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">No Pairs/Bags!</h4>
|
||||
|
||||
<p>Sorry, but your Profit Trailer did not buy anything so far that's worth analyzing.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">DCA and Bought Pairs Analyzer <small id="baglist-refresh-icon"></small></h4>
|
||||
|
||||
<table class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-fieldid="Market" data-tablesaw-sortable-col>Market</th>
|
||||
<th data-fieldid="BoughtTimes" data-tablesaw-sortable-col data-sortable-numeric="true" class="text-right" data-toggle="tooltip" data-placement="top" title="Current DCA level">DCA</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Active buy strategies">Buy Strats</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Current value relevant for buying the next DCA level">BT Value</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Next buy trigger as specified in your DCA properties">Buy Trigger</th>
|
||||
<th data-fieldid="ProfitPercent" data-tablesaw-sortable-col data-sortable-numeric="true" data-tablesaw-sortable-default-col class="text-right tablesaw-sortable-descending" data-toggle="tooltip" data-placement="top" title="Current profit percentage">Profit</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Active sell strategies">Sell Strats</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Active sell trigger value">ST</th>
|
||||
<th data-fieldid="CurrentPrice" data-tablesaw-sortable-col data-sortable-numeric="true" class="text-right" data-toggle="tooltip" data-placement="top" title="Current bid price for this market">Bid Price</th>
|
||||
<th data-fieldid="AverageBuyPrice" data-tablesaw-sortable-col data-sortable-numeric="true" class="text-right" data-toggle="tooltip" data-placement="top" title="Average bought price for this market">Avg. Price</th>
|
||||
<th data-fieldid="TotalCost" data-tablesaw-sortable-col data-sortable-numeric="true" class="text-right" data-toggle="tooltip" data-placement="top" title="Spent total cost in @Model.Summary.MainMarket">Cost</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="content"><tr><td colspan="10"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dca-chart" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog modal-full">
|
||||
<div class="modal-content">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var errCountBagList = [];
|
||||
var interval;
|
||||
var loadBagList = function () {
|
||||
$("#baglist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>');
|
||||
|
||||
var sortedField = $("[data-tablesaw-sortable-default-col]");
|
||||
var sortedFieldId = sortedField.data('fieldid');
|
||||
var sortedDir = 'ASC';
|
||||
if (sortedField.hasClass('tablesaw-sortable-descending')) {
|
||||
sortedDir = 'DESC';
|
||||
}
|
||||
|
||||
$("#content").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/BagList?s=' + sortedFieldId + '&d=' + sortedDir, '', function (responseText, textStatus, XMLHttpRequest) {
|
||||
if (textStatus == 'error') {
|
||||
errCountBagList["BagList"]++;
|
||||
if (errCountBagList["BagList"] > 2) {
|
||||
$.Notification.notify('error', 'top left', 'Bag List update failed!', 'PTMagic Bag List failed to update data. If this error does not go away by itself, please check the connection to your hosting PC.')
|
||||
}
|
||||
} else if (responseText == 'returntologin') {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
|
||||
} else {
|
||||
errCountBagList["BagList"] = 0;
|
||||
}
|
||||
$("#baglist-refresh-icon").html('');
|
||||
$('[role="tooltip"]').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
errCountBagList["BagList"] = 0;
|
||||
loadBagList();
|
||||
interval = setInterval(function () { loadBagList(); }, @Model.PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds * 1000);
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class BagAnalyzerModel : _Internal.BasePageModelSecure {
|
||||
public ProfitTrailerData PTData = null;
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
@page
|
||||
@model BuyAnalyzerModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
|
||||
@if (Model.PTData.BuyLog.Count == 0) {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">No possible buys!</h4>
|
||||
|
||||
<p>Sorry, but your Profit Trailer did not buy anything so far that's worth buying.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Possible Buy Analyzer <small id="buylist-refresh-icon"></small></h4>
|
||||
|
||||
<table class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-fieldid="Market" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Market</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Buy strategy for this market">Buy Strategy</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Current value relevant for buying into this market">BT Value</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Buy trigger as specified in your Pairs properties">Buy Trigger</th>
|
||||
<th data-fieldid="CurrentPrice" data-tablesaw-sortable-col data-sortable-numeric="true" class="text-right" data-toggle="tooltip" data-placement="top" title="Current ask price for this market">Ask Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="content"><tr><td colspan="10"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var errCountBuyList = [];
|
||||
var interval;
|
||||
var loadBuyList = function () {
|
||||
$("#buylist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>');
|
||||
|
||||
var sortedField = $("[data-tablesaw-sortable-default-col]");
|
||||
var sortedFieldId = sortedField.data('fieldid');
|
||||
var sortedDir = 'ASC';
|
||||
if (sortedField.hasClass('tablesaw-sortable-descending')) {
|
||||
sortedDir = 'DESC';
|
||||
}
|
||||
|
||||
$("#content").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/BuyList?s=' + sortedFieldId + '&d=' + sortedDir, '', function (responseText, textStatus, XMLHttpRequest) {
|
||||
if (textStatus == 'error') {
|
||||
errCountBuyList["BuyList"]++;
|
||||
if (errCountBuyList["BuyList"] > 2) {
|
||||
$.Notification.notify('error', 'top left', 'Buy List update failed!', 'PTMagic Buy List failed to update data. If this error does not go away by itself, please check the connection to your hosting PC.')
|
||||
}
|
||||
} else if (responseText == 'returntologin') {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
|
||||
} else {
|
||||
errCountBuyList["BuyList"] = 0;
|
||||
}
|
||||
$("#buylist-refresh-icon").html('');
|
||||
$('[role="tooltip"]').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
errCountBuyList["BuyList"] = 0;
|
||||
loadBuyList();
|
||||
interval = setInterval(function () { loadBuyList(); }, @Model.PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds * 1000);
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class BuyAnalyzerModel : _Internal.BasePageModelSecure {
|
||||
public ProfitTrailerData PTData = null;
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
@model Core.Main.DataObjects.PTMagicData.MarketPairSummary
|
||||
|
||||
@if (Model != null) {
|
||||
if (Model.ActiveSingleSettings != null) {
|
||||
string settingsNames = "";
|
||||
foreach (Core.Main.DataObjects.PTMagicData.SingleMarketSetting sms in Model.ActiveSingleSettings) {
|
||||
if (!settingsNames.Equals("")) {
|
||||
settingsNames += "<br />";
|
||||
}
|
||||
settingsNames += Core.Helper.SystemHelper.SplitCamelCase(sms.SettingName);
|
||||
}
|
||||
<div class='card-box card-box-dark'>
|
||||
<h4 class='m-t-0 header-title text-left'>Active Single Settings</h4>
|
||||
|
||||
<table class='table table-sm'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class='text-left' colspan='2'>Settings</td>
|
||||
<td class='text-right' colspan='2'>@settingsNames</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='text-left'>Buy Value</td>
|
||||
<td class='text-right'>@Model.CurrentBuyValue.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class='text-left'>Trailing Buy</td>
|
||||
<td class='text-right'>@Model.CurrentTrailingBuy.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='text-left'>Sell Value</td>
|
||||
<td class='text-right'>@Model.CurrentSellValue.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class='text-left'>Trailing Profit</td>
|
||||
<td class='text-right'>@Model.CurrentTrailingProfit.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
@page
|
||||
@model DCACalculatorModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
|
||||
}
|
||||
|
||||
@{
|
||||
string maxCostCaption = "Max";
|
||||
if (Model.Summary.ProfitTrailerMajorVersion > 1) {
|
||||
maxCostCaption = "Initial";
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">DCA Calculator</h4>
|
||||
|
||||
<p class="m-b-20">
|
||||
Use this calculator to help building a proper setup for your Profit Trailer settings for <b>ALL_max_trading_pairs</b>, <b>ALL_@maxCostCaption.ToLower()_cost</b> and <b>DCA levels</b>.<br />
|
||||
To do so enter your available balance and adjust the values to whatever you consider the best setting for your parameters.<br />
|
||||
If you are having problems understanding the different modes, <a href="https://github.com/Legedric/ptmagic/wiki/DCA-Calculator" target="_blank">please read this wiki article</a>.
|
||||
</p>
|
||||
|
||||
@if (Model.PTMagicConfiguration.GeneralSettings.Monitor.DefaultDCAMode.Equals("simple", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-6"><button type="button" class="btn btn-block btn-dca-mode btn-lg btn-ptmagic btn-dca-mode-active waves-effect waves-light" data-dca-mode="simple">Simple Mode</button></div>
|
||||
<div class="col-md-6"><button type="button" class="btn btn-block btn-dca-mode btn-lg btn-ptmagic btn-custom waves-effect waves-light" data-dca-mode="advanced">Advanced Mode</button></div>
|
||||
</div>
|
||||
} else {
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-6"><button type="button" class="btn btn-block btn-dca-mode btn-lg btn-ptmagic btn-custom waves-effect waves-light" data-dca-mode="simple">Simple Mode</button></div>
|
||||
<div class="col-md-6"><button type="button" class="btn btn-block btn-dca-mode btn-lg btn-ptmagic btn-dca-mode-active waves-effect waves-light" data-dca-mode="advanced">Advanced Mode</button></div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
In <i>simple mode</i> the DCA calculator will only take your number of pairs, DCA levels and initial cost and will calculate the necessary fundings for you. In this mode the ANDERSON method is quite inaccurate as it will calculate the DCA steps by <b>using the the simple proportional cost</b> for each DCA level, regardless of what level the price may be at the time the DCA buy will happen.
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
In <i>advanced mode</i> the DCA calculator will also take your DCA triggers into account for the calculation of the necessary fundings for you. In this mode the ANDERSON method is being calculated more accurate as it will calculate the necessary costs using your buy triggers to calculate the actual <b>cost of coins at the current price</b> according to your triggers and percentages.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Total Balance <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Value is being read from your settings and added with your total profit so far."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="dca-capital" class="dca-input form-control" value="@Model.PTData.GetCurrentBalance().ToString("0.00000000", new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Number of Pairs <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Value is being read from your PAIRS.PROPERTIES"></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="dca-pairs" class="dca-input form-control" value="@Model.Summary.MaxTradingPairs"><input type="hidden" id="dca-maxpairs" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDCAPairs">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">DCA Levels <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Value is being read from your DCA.PROPERTIES"></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" id="dca-levels" class="dca-input form-control" value="@Model.Summary.DCALevels">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@{
|
||||
double maxCostValue = Model.Summary.MaxCost;
|
||||
if (maxCostValue == 0) {
|
||||
maxCostValue = Model.Summary.MaxCostPercentage;
|
||||
}
|
||||
}
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">@maxCostCaption Cost <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Value is being read from your PAIRS.PROPERTIES"></i></label>
|
||||
<div class="col-md-5">
|
||||
<input type="text" id="dca-max-cost" class="dca-input form-control" value="@maxCostValue.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select id="dca-max-cost-type" class="dca-input form-control">
|
||||
@if (Model.Summary.MaxCost > 0) {
|
||||
<option value="1" selected="selected">Value</option>
|
||||
} else {
|
||||
<option value="1">Value</option>
|
||||
}
|
||||
@if (Model.Summary.MaxCostPercentage > 0 && Model.Summary.MaxCost == 0) {
|
||||
<option value="2" selected="selected">Percent</option>
|
||||
} else {
|
||||
<option value="2">Percent</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card-box" style="min-height:250px;">
|
||||
<p id="dca-noresult"><i>Please enter a valid balance value on the left to calculate results...</i></p>
|
||||
|
||||
<table id="dca-result-table" class="table table-striped" style="display:none;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Cost Per Pair</td>
|
||||
<td class="text-right"><span id="dca-cost-pair"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Balance Needed</td>
|
||||
<td class="text-right"><span id="dca-capital-needed"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Result</td>
|
||||
<td class="text-right"><span id="dca-result"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balance Extended %</td>
|
||||
<td class="text-right"><span id="dca-result-percent"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="row-dca-triggers" class="row @if (Model.PTMagicConfiguration.GeneralSettings.Monitor.DefaultDCAMode.Equals("simple", StringComparison.InvariantCultureIgnoreCase)) { <text>hidden</text> }">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">DCA Triggers <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Your DCA buy triggers set in your DCA.PROPERTIES"></i></h4>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>DCA Level</th>
|
||||
<th class="text-right">Trigger %</th>
|
||||
<th class="text-right">Buy %</th>
|
||||
<th class="text-right">Approx. Drop %</th>
|
||||
<th class="text-right">Simple Cost / Pair</th>
|
||||
<th class="text-right">Advanced Cost / Pair</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td class="dca-trigger-0 text-right text-autocolor" data-triggervalue="@Model.Summary.BuyValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))">@Model.Summary.BuyValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
<td class="dca-percentage-0 text-right"></td>
|
||||
<td class="dca-drop-1-0 text-right text-autocolor"></td>
|
||||
<td class="dca-1-0-simp text-right"></td>
|
||||
<td class="dca-1-0-adv text-right"></td>
|
||||
</tr>
|
||||
@for (int l = 1; l <= (int)Model.Summary.DCALevels; l++) {
|
||||
double dcaPercentage = Model.Summary.DCAPercentage;
|
||||
if (Model.Summary.DCAPercentages.ContainsKey(l)) {
|
||||
dcaPercentage = Model.Summary.DCAPercentages[l];
|
||||
}
|
||||
|
||||
double dcaTrigger = Model.Summary.DCATrigger;
|
||||
if (Model.Summary.DCATriggers.ContainsKey(l)) {
|
||||
dcaTrigger = Model.Summary.DCATriggers[l];
|
||||
}
|
||||
<tr>
|
||||
<td>@l</td>
|
||||
<td class="dca-trigger-@l text-right text-autocolor" data-triggervalue="@dcaTrigger.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))">@dcaTrigger.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
<td class="dca-percentage-@l text-right" data-percentagevalue="@dcaPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))">@dcaPercentage.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
<td class="dca-drop-1-@l text-right text-autocolor"></td>
|
||||
<td class="dca-1-@l-simp text-right"></td>
|
||||
<td class="dca-1-@l-adv text-right"></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@{
|
||||
int maxDCALevels = 12;
|
||||
if (Model.Summary.DCALevels > maxDCALevels) {
|
||||
maxDCALevels = (int)Model.Summary.DCALevels;
|
||||
}
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">DCA Overview</h4>
|
||||
<div class="table-scroll-hori">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-right"></th>
|
||||
@for (int col = 1; col <= maxDCALevels; col++) {
|
||||
<th class="text-right">DCA @col</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int row = 1; row <= Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDCAPairs; row++) {
|
||||
<tr>
|
||||
<th>@row Pairs</th>
|
||||
@for (int col = 1; col <= maxDCALevels; col++) {
|
||||
<td class="dca-@row-@col text-right"></td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/dca.js?v=@Html.Raw(Model.Summary.Version)"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('.dca-input').trigger('input');
|
||||
|
||||
$('.btn-dca-mode').click(function () {
|
||||
var mode = $(this).data('dca-mode');
|
||||
|
||||
$('.btn-dca-mode').removeClass('btn-dca-mode-active');
|
||||
$('.btn-dca-mode').addClass('btn-custom');
|
||||
$(this).removeClass('btn-custom');
|
||||
$(this).addClass('btn-dca-mode-active');
|
||||
|
||||
if (mode == 'advanced') {
|
||||
$('#row-dca-triggers').removeClass('hidden');
|
||||
|
||||
} else {
|
||||
if (!$('#row-dca-triggers').hasClass('hidden')) {
|
||||
$('#row-dca-triggers').addClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
$('.dca-input').trigger('input');
|
||||
});
|
||||
})
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.Linq;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class DCACalculatorModel : _Internal.BasePageModelSecure {
|
||||
public ProfitTrailerData PTData = null;
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
@page
|
||||
@model ErrorModel
|
||||
@{
|
||||
Layout = null;
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
@if (Model.Exception != null) {
|
||||
<h2>@Model.Exception.Error.Message</h2>
|
||||
<p>@Model.Exception.Error.StackTrace</p>
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class ErrorModel : PageModel {
|
||||
public string RequestId { get; set; }
|
||||
public IExceptionHandlerFeature Exception = null;
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet() {
|
||||
Exception = HttpContext.Features.Get<IExceptionHandlerFeature>();
|
||||
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
@page
|
||||
@model IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
|
||||
<div id="dashboardTop"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>
|
||||
<div id="dashboardBottom"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>
|
||||
|
||||
<div id="dca-chart" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog modal-full">
|
||||
<div class="modal-content">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
|
||||
@section Scripts {
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/d3/d3.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
var errCountIndex = [];
|
||||
var intervalDashboardTop;
|
||||
var intervalDashboardBottom;
|
||||
var loadDashboardTop = function () {
|
||||
$("#baglist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>');
|
||||
$("#buylist-refresh-icon").html('<i class="fa fa-circle-o-notch fa-spin fa-fw" data-toggle="tooltip" data-placement="top" title="Loading fresh data..."></i>');
|
||||
|
||||
$("#dashboardTop").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/DashboardTop', '', function (responseText, textStatus, XMLHttpRequest) {
|
||||
$("#baglist-refresh-icon").html('');
|
||||
$("#buylist-refresh-icon").html('');
|
||||
$('[role="tooltip"]').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
|
||||
if (textStatus == 'error') {
|
||||
errCountIndex["DashboardTop"]++;
|
||||
if (errCountIndex["DashboardTop"] > 2) {
|
||||
$.Notification.notify('error', 'top left', 'Dashboard (Top) update failed!', 'PTMagic Monitor failed to update data. If this error does not go away by itself, please check the connection to your hosting PC.')
|
||||
}
|
||||
} else if (responseText == 'returntologin') {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
|
||||
} else {
|
||||
errCountIndex["DashboardTop"] = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var loadDashboardBottom = function () {
|
||||
//destroy all d3 svg graph to avoid memory leak
|
||||
$(".nvtooltip").remove();
|
||||
$("svg > *").remove();
|
||||
$("svg").remove();
|
||||
nv.charts = {};
|
||||
nv.graphs = [];
|
||||
nv.logs = {};
|
||||
nv.tooltip = {};
|
||||
|
||||
$("#dashboardBottom").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/DashboardBottom', '', function (responseText, textStatus, XMLHttpRequest) {
|
||||
if (textStatus == 'error') {
|
||||
errCountIndex["DashboardBottom"]++;
|
||||
if (errCountIndex["DashboardBottom"] > 2) {
|
||||
$.Notification.notify('error', 'top left', 'Dashboard (Bottom) update failed!', 'PTMagic Monitor failed to update data. If this error does not go away by itself, please check the connection to your hosting PC.')
|
||||
}
|
||||
} else if (responseText == 'returntologin') {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
|
||||
} else {
|
||||
errCountIndex["DashboardBottom"] = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
errCountIndex["DashboardTop"] = 0;
|
||||
loadDashboardTop();
|
||||
intervalDashboardTop = setInterval(function () { loadDashboardTop(); }, 5 * 1000);
|
||||
|
||||
errCountIndex["DashboardBottom"] = 0;
|
||||
loadDashboardBottom();
|
||||
intervalDashboardBottom = setInterval(function () { loadDashboardBottom(); }, @Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds * 1000);
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Monitor.Pages {
|
||||
public class IndexModel : _Internal.BasePageModelSecure {
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
@page
|
||||
@model LoginModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
Layout = "_LayoutPublic";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
|
||||
}
|
||||
|
||||
|
||||
<div class="card-box">
|
||||
|
||||
<div class="p-20">
|
||||
|
||||
<form method="post" role="form" class="text-center">
|
||||
<div class="form-group">
|
||||
<h3>PT Magic</h3>
|
||||
<p class="text-muted">
|
||||
Please enter your password.
|
||||
</p>
|
||||
<div class="input-group m-t-30 m-b-20">
|
||||
<input name="password" type="password" class="form-control" placeholder="Password" required="">
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-ptmagic w-sm waves-effect waves-light">
|
||||
Log In
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<p><input type="checkbox" name="cbRememberMe"/> <label for="cbRememberMe">Stay logged in</label></p>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<p>
|
||||
Need to set a new password?<br />Delete "<i class="text-ptmagic">settings.secure.json</i>" from your PT Magic directory!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class LoginModel : _Internal.BasePageModel {
|
||||
public string CurrentPassword = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.PreInit();
|
||||
|
||||
CurrentPassword = PTMagicConfiguration.SecureSettings.MonitorPassword;
|
||||
if (CurrentPassword.Equals("")) {
|
||||
Response.Redirect(PTMagicConfiguration.GeneralSettings.Monitor.RootUrl + "SetupPassword");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPost(string password, string cbRememberMe) {
|
||||
base.PreInit();
|
||||
|
||||
string encryptedPassword = EncryptionHelper.Encrypt(password);
|
||||
|
||||
if (encryptedPassword.Equals(PTMagicConfiguration.SecureSettings.MonitorPassword)) {
|
||||
HttpContext.Session.SetString("LoggedIn" + PTMagicConfiguration.GeneralSettings.Monitor.Port.ToString(), DateTime.Now.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'"));
|
||||
|
||||
if (cbRememberMe != null) {
|
||||
if (cbRememberMe.Equals("on", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
CookieOptions cookieOption = new CookieOptions();
|
||||
cookieOption.Expires = DateTime.Now.AddYears(1);
|
||||
|
||||
string cookieValue = EncryptionHelper.Encrypt(encryptedPassword);
|
||||
|
||||
Response.Cookies.Append("PTMRememberMeKey", cookieValue, cookieOption);
|
||||
}
|
||||
}
|
||||
|
||||
Response.Redirect(PTMagicConfiguration.GeneralSettings.Monitor.RootUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
@page
|
||||
@model ManageSMSModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
}
|
||||
|
||||
<form class="form-horizontal" method="post">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card-box">
|
||||
|
||||
<h4 class="m-t-0 header-title"><b>Applied Single Market Setting Details</b></h4>
|
||||
|
||||
<table class="table table-striped table-sm m-b-0" h>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Market Name</th>
|
||||
<th class="text-right">Last Price</th>
|
||||
<th class="text-right">Volume</th>
|
||||
<th>Active Setting</th>
|
||||
<th>Triggered time</th>
|
||||
<th>Off Triggers</th>
|
||||
<th class="text-right">Current Values</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{
|
||||
string lastMarket = "";
|
||||
}
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.SingleMarketSettingSummary smsSummary in Model.SingleMarketSettingSummaries.OrderBy(s => s.Market)) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = null;
|
||||
if (Model.Summary.MarketSummary.ContainsKey(smsSummary.Market)) {
|
||||
mps = Model.Summary.MarketSummary[smsSummary.Market];
|
||||
} else {
|
||||
mps = new Core.Main.DataObjects.PTMagicData.MarketPairSummary();
|
||||
}
|
||||
|
||||
bool settingHasOffTriggers = false;
|
||||
if (smsSummary.SingleMarketSetting.OffTriggers.Count > 0) {
|
||||
settingHasOffTriggers = true;
|
||||
}
|
||||
|
||||
int activeSeconds = (int)Math.Floor(DateTime.UtcNow.Subtract(smsSummary.ActivationDateTimeUTC).TotalSeconds);
|
||||
int activeHours = (int)Math.Floor(DateTime.UtcNow.Subtract(smsSummary.ActivationDateTimeUTC).TotalHours);
|
||||
<tr>
|
||||
<th>
|
||||
@if (!lastMarket.Equals(smsSummary.Market)) {
|
||||
<a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, smsSummary.Market, Model.Summary.MainMarket)" target="_blank">@smsSummary.Market</a>
|
||||
}
|
||||
</th>
|
||||
<td class="text-right">
|
||||
@if (!lastMarket.Equals(smsSummary.Market)) {
|
||||
<span>@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@if (!lastMarket.Equals(smsSummary.Market)) {
|
||||
<span>@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@Core.Helper.SystemHelper.SplitCamelCase(smsSummary.SingleMarketSetting.SettingName)
|
||||
</td>
|
||||
<td>
|
||||
@Core.Helper.SystemHelper.GetProperDurationTime(activeSeconds)
|
||||
</td>
|
||||
<td>
|
||||
@if (settingHasOffTriggers) {
|
||||
foreach (Core.Main.DataObjects.PTMagicData.OffTrigger ot in smsSummary.SingleMarketSetting.OffTriggers) {
|
||||
string triggerTextClass = "text-danger";
|
||||
if (ot.HoursSinceTriggered > 0) {
|
||||
triggerTextClass = (activeHours >= ot.HoursSinceTriggered) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass"><span>Hours Since Triggered</span><span class="pull-right">@ot.HoursSinceTriggered</span></p>
|
||||
}
|
||||
if (ot.Min24hVolume > 0 || ot.Max24hVolume < Core.Main.Constants.Max24hVolume) {
|
||||
triggerTextClass = (mps.Latest24hVolume >= ot.Min24hVolume && mps.Latest24hVolume <= ot.Max24hVolume) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass">
|
||||
<span>Volume</span>
|
||||
<span class="pull-right">
|
||||
@if (ot.Min24hVolume > 0) {
|
||||
@("min: " + ot.Min24hVolume.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) + " " + Model.Summary.MainMarket)
|
||||
}
|
||||
@if (ot.Min24hVolume > 0 && ot.Max24hVolume < Core.Main.Constants.Max24hVolume) {
|
||||
@(", ")
|
||||
}
|
||||
@if (ot.Max24hVolume < Core.Main.Constants.Max24hVolume) {
|
||||
@("max: " + ot.Max24hVolume.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) + " " + Model.Summary.MainMarket)
|
||||
}
|
||||
</span>
|
||||
</p>
|
||||
}
|
||||
if (ot.Max24hVolume < Core.Main.Constants.Max24hVolume) {
|
||||
triggerTextClass = (mps.Latest24hVolume <= ot.Max24hVolume) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass"><span>Max 24h Volume</span><span class="pull-right">@ot.Max24hVolume.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</span></p>
|
||||
}
|
||||
if (!ot.MarketTrendName.Equals("")) {
|
||||
double trendChange = Model.GetTrendChange(ot.MarketTrendName, mps, smsSummary.TriggerSnapshot, ot.MarketTrendRelation);
|
||||
triggerTextClass = (trendChange >= ot.MinChange && trendChange <= ot.MaxChange) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass">
|
||||
<span>@Core.Helper.SystemHelper.SplitCamelCase(ot.MarketTrendName) (@Core.Helper.SystemHelper.SplitCamelCase(ot.MarketTrendRelation))</span>
|
||||
<span class="pull-right">
|
||||
@if (ot.MinChange > Core.Main.Constants.MinTrendChange) {
|
||||
@("min: " + ot.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%")
|
||||
}
|
||||
@if (ot.MinChange > Core.Main.Constants.MinTrendChange && ot.MaxChange < Core.Main.Constants.MaxTrendChange) {
|
||||
@(", ")
|
||||
}
|
||||
@if (ot.MaxChange < Core.Main.Constants.MaxTrendChange) {
|
||||
@("max: " + ot.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%")
|
||||
}
|
||||
</span>
|
||||
</p>
|
||||
}
|
||||
}
|
||||
} else {
|
||||
<i class="text-warning">Not specified</i> <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This setting has no Off Triggers so it will stay active until its triggers are no longer matched."></i>
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@if (settingHasOffTriggers) {
|
||||
foreach (Core.Main.DataObjects.PTMagicData.OffTrigger ot in smsSummary.SingleMarketSetting.OffTriggers) {
|
||||
string triggerTextClass = "text-danger";
|
||||
if (ot.HoursSinceTriggered > 0) {
|
||||
triggerTextClass = (activeHours >= ot.HoursSinceTriggered) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass">@activeHours.ToString(new System.Globalization.CultureInfo("en-US"))</p>
|
||||
}
|
||||
if (ot.Min24hVolume > 0) {
|
||||
triggerTextClass = (mps.Latest24hVolume >= ot.Min24hVolume) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</p>
|
||||
}
|
||||
if (ot.Max24hVolume < Core.Main.Constants.Max24hVolume) {
|
||||
triggerTextClass = (mps.Latest24hVolume <= ot.Max24hVolume) ? "text-success" : "text-danger";
|
||||
<p class="@triggerTextClass">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</p>
|
||||
}
|
||||
if (!ot.MarketTrendName.Equals("")) {
|
||||
if (mps.MarketTrendChanges.ContainsKey(ot.MarketTrendName)) {
|
||||
double trendChange = Model.GetTrendChange(ot.MarketTrendName, mps, smsSummary.TriggerSnapshot, ot.MarketTrendRelation);
|
||||
triggerTextClass = (trendChange >= ot.MinChange && trendChange <= ot.MaxChange) ? "text-success" : "text-danger";
|
||||
string trendChangeOutput = trendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
<p class="@triggerTextClass">@trendChangeOutput%</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (settingHasOffTriggers) {
|
||||
@if (Model.PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected) {
|
||||
<a class="btn btn-danger btn-sm btn-custom btn-block text-uppercase btn-resetsettingsecure" data-datatarget="@Html.Raw(smsSummary.Market)" data-setting="@Html.Raw(smsSummary.SingleMarketSetting.SettingName)" href="#">Reset</a>
|
||||
} else {
|
||||
<a class="btn btn-danger btn-custom btn-block text-uppercase" data-toggle="tooltip" data-placement="top" title="This is only accessible when you protect your monitor with a password!"><i class="fa fa-lock text-danger"></i> Reset</a>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
lastMarket = smsSummary.Market;
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalResetSetting" tabindex="-1" role="dialog" aria-labelledby="modalResetSettingTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalResetSettingTitle">Are you sure?</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do you really want to reset this Single Market Setting during the next run of PT Magic?
|
||||
<p class="m-t-10"><span class="text-warning">Please note:</span> Even if you reset a setting, it may get triggered again on the next run depending on current market conditions.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-resetsetting" data-datatarget="" data-setting="">Yes, do it!</button>
|
||||
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">No...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('.btn-resetsettingsecure').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataSetting = $(this).data('setting');
|
||||
|
||||
$('.btn-resetsetting').data('datatarget', dataTarget);
|
||||
$('.btn-resetsetting').data('setting', dataSetting);
|
||||
$('#modalResetSetting').modal('show');
|
||||
});
|
||||
|
||||
$('.btn-resetsetting').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataSetting = $(this).data('setting');
|
||||
|
||||
var postValues = { Market: dataTarget, Setting: dataSetting };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/ResetSingleMarketSetting",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(postValues),
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("XSRF-TOKEN",
|
||||
$('input:hidden[name="__RequestVerificationToken"]').val());
|
||||
},
|
||||
success: function (data) {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)ManageSMS?n=SettingReset");
|
||||
},
|
||||
error: function (jqxhr, errorText, thrownError) {
|
||||
$.Notification.notify('error', 'top left', 'Error resetting setting!', 'Error message: ' + errorText);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@if (!Model.NotifyType.Equals("") && !Model.NotifyHeadline.Equals("") && !Model.NotifyMessage.Equals("")) {
|
||||
<text>
|
||||
$.Notification.notify('@Model.NotifyType', 'top left', '@Model.NotifyHeadline', '@Model.NotifyMessage');
|
||||
window.history.pushState('@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor', '@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor', '@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)ManageSMS');
|
||||
</text>
|
||||
}
|
||||
})
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class ManageSMSModel : _Internal.BasePageModelSecure {
|
||||
public List<SingleMarketSettingSummary> SingleMarketSettingSummaries = new List<SingleMarketSettingSummary>();
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
if (System.IO.File.Exists(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "SingleMarketSettingSummary.json")) {
|
||||
try {
|
||||
SingleMarketSettingSummaries = JsonConvert.DeserializeObject<List<SingleMarketSettingSummary>>(System.IO.File.ReadAllText(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "SingleMarketSettingSummary.json"));
|
||||
} catch { }
|
||||
}
|
||||
|
||||
string notification = GetStringParameter("n", "");
|
||||
if (notification.Equals("SettingReset")) {
|
||||
NotifyHeadline = "Setting Reset!";
|
||||
NotifyMessage = "The setting will get reset on the next interval!";
|
||||
NotifyType = "success";
|
||||
}
|
||||
}
|
||||
|
||||
public double GetTrendChange(string marketTrend, MarketPairSummary mps, TriggerSnapshot ts, string marketTrendRelation) {
|
||||
double result = 0;
|
||||
|
||||
if (mps.MarketTrendChanges.ContainsKey(marketTrend)) {
|
||||
result = mps.MarketTrendChanges[marketTrend];
|
||||
double averageMarketTrendChange = Summary.MarketTrendChanges[marketTrend].OrderByDescending(mtc => mtc.TrendDateTime).First().TrendChange;
|
||||
if (marketTrendRelation.Equals(Constants.MarketTrendRelationAbsolute, StringComparison.InvariantCulture)) {
|
||||
result = result - averageMarketTrendChange;
|
||||
} else if (marketTrendRelation.Equals(Constants.MarketTrendRelationRelativeTrigger, StringComparison.InvariantCulture)) {
|
||||
double currentPrice = mps.LatestPrice;
|
||||
double triggerPrice = ts.LastPrice;
|
||||
double triggerTrend = (currentPrice - triggerPrice) / triggerPrice * 100;
|
||||
result = triggerTrend;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
@page
|
||||
@model MarketAnalyzerModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 m-b-20 header-title"><b>Market Trend Averages</b></h4>
|
||||
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-right">Markets</th>
|
||||
<th class="text-right">Timeframe</th>
|
||||
<th class="text-right">Change</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend marketTrend in Model.MarketTrends.OrderBy(mt => mt.TrendMinutes)) {
|
||||
if (Model.Summary.MarketTrendChanges.ContainsKey(marketTrend.Name)) {
|
||||
double trendChange = Model.Summary.MarketTrendChanges[marketTrend.Name].OrderByDescending(mtc => mtc.TrendDateTime).First().TrendChange;
|
||||
string trendChangeOutput = trendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
|
||||
int marketCount = marketTrend.MaxMarkets;
|
||||
string marketCountString = marketCount.ToString();
|
||||
|
||||
if (marketCount == 0) {
|
||||
marketCountString = "All";
|
||||
} else if (marketCount > Model.Summary.MarketSummary.Keys.Count && marketTrend.Platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
marketCountString = Model.Summary.MarketSummary.Keys.Count.ToString();
|
||||
}
|
||||
<tr>
|
||||
<td>@Core.Helper.SystemHelper.SplitCamelCase(marketTrend.Name)</td>
|
||||
<td class="text-right">@marketCountString</td>
|
||||
<td class="text-right">@Core.Helper.SystemHelper.GetProperDurationTime(marketTrend.TrendMinutes * 60, false)</td>
|
||||
<td class="text-right text-autocolor">@trendChangeOutput%</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if (!Model.TrendChartDataJSON.Equals("")) {
|
||||
<div class="trend-chart">
|
||||
<svg style="height:205px;width:100%"></svg>
|
||||
</div>
|
||||
} else {
|
||||
<p>Not able to load graph, no market trend data found.<br />If you still do not see a graph after more than hour, report an issue.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card-box">
|
||||
|
||||
<h4 class="m-t-0 header-title"><b>Trend Overview for @Model.Summary.MarketSummary.Keys.Count markets on @Model.PTMagicConfiguration.GeneralSettings.Application.Exchange</b></h4>
|
||||
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-6"><button type="button" class="btn btn-block btn-trend-relation btn-lg btn-ptmagic waves-effect waves-light" data-trend-relation="absolute">Absolute Trend Changes</button></div>
|
||||
<div class="col-md-6"><button type="button" class="btn btn-block btn-trend-relation btn-lg btn-ptmagic btn-custom waves-effect waves-light" data-trend-relation="relative">Relative Trend Changes</button></div>
|
||||
</div>
|
||||
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-6">
|
||||
The <i>absolute trend changes</i> are the trend changes for each coin on its own for the specified timeframe without putting the change into any relation with the overall market.
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
The <i>relative trend changes</i> are the trend changes for each coin in relation (or compared) to the overall market trend for the specified timeframe.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@{
|
||||
List<Core.Main.DataObjects.PTMagicData.MarketTrend> marketTrends = Model.MarketTrends.FindAll(mt => mt.Platform.Equals("Exchange") && mt.DisplayOnMarketAnalyzerList);
|
||||
}
|
||||
|
||||
<table id="trends-absolute" class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-toggle="tooltip" data-placement="top" title="Trading status">TRA</th>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-toggle="tooltip" data-placement="top" title="Sell Only Mode status">SOM</th>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-toggle="tooltip" data-placement="top" title="DCA status">DCA</th>
|
||||
<th scope="col" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Market Name</th>
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">Last Price</th>
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">Volume</th>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend marketTrend in marketTrends) {
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">@Core.Helper.SystemHelper.SplitCamelCase(marketTrend.Name)</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (string market in Model.Summary.MarketSummary.Keys.OrderBy(m => m)) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = Model.Summary.MarketSummary[market];
|
||||
int marketTrendsDisplayed = 0;
|
||||
<tr>
|
||||
<th>
|
||||
@if (mps.IsTradingEnabled) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</th>
|
||||
<th>
|
||||
@if (mps.IsSOMActive) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</th>
|
||||
<th>
|
||||
@if (mps.IsDCAEnabled) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</th>
|
||||
@if (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
|
||||
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a></th>
|
||||
} else {
|
||||
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
|
||||
}
|
||||
<td class="text-right">@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td>
|
||||
<td class="text-right">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainMarket</td>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend marketTrend in marketTrends) {
|
||||
if (mps.MarketTrendChanges.ContainsKey(marketTrend.Name)) {
|
||||
marketTrendsDisplayed++;
|
||||
string trendChangeOutput = mps.MarketTrendChanges[marketTrend.Name].ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
<td class="text-right text-autocolor-saw">@trendChangeOutput%</td>
|
||||
}
|
||||
}
|
||||
@for (int i = 0; i < marketTrends.Count - marketTrendsDisplayed; i++) {
|
||||
<td></td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table id="trends-relative" class="hidden tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-toggle="tooltip" data-placement="top" title="Trading status">TRA</th>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-toggle="tooltip" data-placement="top" title="Sell Only Mode status">SOM</th>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-toggle="tooltip" data-placement="top" title="DCA status">DCA</th>
|
||||
<th scope="col" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Market Name</th>
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">Last Price</th>
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">Volume</th>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend marketTrend in marketTrends) {
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">@Core.Helper.SystemHelper.SplitCamelCase(marketTrend.Name)</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (string market in Model.Summary.MarketSummary.Keys.OrderBy(m => m)) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = Model.Summary.MarketSummary[market];
|
||||
int marketTrendsDisplayed = 0;
|
||||
<tr>
|
||||
<th>
|
||||
@if (Model.Summary.MarketSummary[market].IsTradingEnabled) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</th>
|
||||
<th>
|
||||
@if (Model.Summary.MarketSummary[market].IsSOMActive) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</th>
|
||||
<th>
|
||||
@if (Model.Summary.MarketSummary[market].IsDCAEnabled) {
|
||||
<i class="fa fa-check"></i>
|
||||
}
|
||||
</th>
|
||||
@if (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
|
||||
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a></th>
|
||||
} else {
|
||||
<th><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, market, Model.Summary.MainMarket)" target="_blank">@market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
|
||||
}
|
||||
<td class="text-right">@mps.LatestPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@Math.Round(mps.Latest24hVolume, 0).ToString("#,#0", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend marketTrend in marketTrends) {
|
||||
if (mps.MarketTrendChanges.ContainsKey(marketTrend.Name)) {
|
||||
marketTrendsDisplayed++;
|
||||
double relativeTrend = Model.Summary.MarketSummary[market].MarketTrendChanges[marketTrend.Name] - Model.Summary.MarketTrendChanges[marketTrend.Name].OrderByDescending(mtc => mtc.TrendDateTime).First().TrendChange;
|
||||
string trendChangeOutput = relativeTrend.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
<td class="text-right text-autocolor-saw">@trendChangeOutput%</td>
|
||||
}
|
||||
}
|
||||
@for (int i = 0; i < marketTrends.Count - marketTrendsDisplayed; i++) {
|
||||
<td></td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/d3/d3.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js?v=@Html.Raw(Model.Summary.Version)"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('.btn-trend-relation').click(function () {
|
||||
var relation = $(this).data('trend-relation');
|
||||
|
||||
$('.btn-trend-relation').addClass('btn-custom');
|
||||
$(this).removeClass('btn-custom');
|
||||
|
||||
if (relation == 'absolute') {
|
||||
$('#trends-absolute').removeClass('hidden');
|
||||
$('#trends-relative').addClass('hidden');
|
||||
|
||||
} else {
|
||||
$('#trends-absolute').addClass('hidden');
|
||||
$('#trends-relative').removeClass('hidden');
|
||||
}
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
@if (!Model.TrendChartDataJSON.Equals("")) {
|
||||
<text>
|
||||
nv.addGraph(function () {
|
||||
var lineChart = nv.models.lineChart();
|
||||
var height = 300;
|
||||
var chartData = @Html.Raw(Model.TrendChartDataJSON);
|
||||
lineChart.useInteractiveGuideline(true);
|
||||
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M')(new Date(d)); });
|
||||
lineChart.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f'));
|
||||
d3.select('.trend-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
|
||||
//nv.utils.windowResize(lineChart.update); v1.3.0 => Removed this line to prevent memory leak
|
||||
|
||||
return lineChart;
|
||||
});
|
||||
</text>
|
||||
}
|
||||
})
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class MarketAnalyzerModel : _Internal.BasePageModelSecure {
|
||||
public List<MarketTrend> MarketTrends { get; set; } = new List<MarketTrend>();
|
||||
public string TrendChartDataJSON = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
// Get market trends
|
||||
MarketTrends = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.OrderBy(mt => mt.TrendMinutes).ThenByDescending(mt => mt.Platform).ToList();
|
||||
|
||||
BuildMarketTrendChartData();
|
||||
}
|
||||
|
||||
private void BuildMarketTrendChartData() {
|
||||
if (MarketTrends.Count > 0) {
|
||||
TrendChartDataJSON = "[";
|
||||
int mtIndex = 0;
|
||||
foreach (MarketTrend mt in MarketTrends) {
|
||||
if (mt.DisplayGraph) {
|
||||
string lineColor = "";
|
||||
if (mtIndex < Constants.ChartLineColors.Length) {
|
||||
lineColor = Constants.ChartLineColors[mtIndex];
|
||||
} else {
|
||||
lineColor = Constants.ChartLineColors[mtIndex - 20];
|
||||
}
|
||||
|
||||
if (Summary.MarketTrendChanges.ContainsKey(mt.Name)) {
|
||||
List<MarketTrendChange> marketTrendChangeSummaries = Summary.MarketTrendChanges[mt.Name];
|
||||
|
||||
if (marketTrendChangeSummaries.Count > 0) {
|
||||
if (!TrendChartDataJSON.Equals("[")) TrendChartDataJSON += ",";
|
||||
|
||||
TrendChartDataJSON += "{";
|
||||
TrendChartDataJSON += "key: '" + SystemHelper.SplitCamelCase(mt.Name) + "',";
|
||||
TrendChartDataJSON += "color: '" + lineColor + "',";
|
||||
TrendChartDataJSON += "values: [";
|
||||
|
||||
// Get trend ticks for chart
|
||||
DateTime currentDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, 0, 0);
|
||||
DateTime startDateTime = currentDateTime.AddHours(-PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours);
|
||||
DateTime endDateTime = currentDateTime;
|
||||
int trendChartTicks = 0;
|
||||
for (DateTime tickTime = startDateTime; tickTime <= endDateTime; tickTime = tickTime.AddMinutes(PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes)) {
|
||||
List<MarketTrendChange> tickRange = marketTrendChangeSummaries.FindAll(m => m.TrendDateTime >= tickTime).OrderBy(m => m.TrendDateTime).ToList();
|
||||
if (tickRange.Count > 0) {
|
||||
MarketTrendChange mtc = tickRange.First();
|
||||
if (tickTime != startDateTime) TrendChartDataJSON += ",\n";
|
||||
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
|
||||
|
||||
TrendChartDataJSON += "{ x: new Date('" + tickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
|
||||
trendChartTicks++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add most recent tick
|
||||
List<MarketTrendChange> latestTickRange = marketTrendChangeSummaries.OrderByDescending(m => m.TrendDateTime).ToList();
|
||||
if (latestTickRange.Count > 0) {
|
||||
MarketTrendChange mtc = latestTickRange.First();
|
||||
if (trendChartTicks > 0) TrendChartDataJSON += ",\n";
|
||||
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
|
||||
|
||||
TrendChartDataJSON += "{ x: new Date('" + mtc.TrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
|
||||
}
|
||||
|
||||
TrendChartDataJSON += "]";
|
||||
TrendChartDataJSON += "}";
|
||||
|
||||
mtIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TrendChartDataJSON += "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
@page
|
||||
@model PresetFilesModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<style type="text/css" media="screen">
|
||||
|
||||
#editor {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#preset-file {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Preset Files</h4>
|
||||
|
||||
<p>
|
||||
Use this list to edit your preset files for PTM.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.GlobalSetting globalSetting in Model.GlobalSettingsWithPresets) {
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">
|
||||
@Core.Helper.SystemHelper.SplitCamelCase(globalSetting.SettingName)
|
||||
</h4>
|
||||
|
||||
<ul>
|
||||
@if (globalSetting.PairsProperties.ContainsKey("File")) {
|
||||
<li><a class="btn-edit-presetfile" data-settingname="@Html.Raw(globalSetting.SettingName)" data-datatarget="@Html.Raw(globalSetting.PairsProperties["File"])" href="#">@Html.Raw(globalSetting.PairsProperties["File"])</a></li>
|
||||
}
|
||||
@if (globalSetting.DCAProperties.ContainsKey("File")) {
|
||||
<li><a class="btn-edit-presetfile" data-settingname="@Html.Raw(globalSetting.SettingName)" data-datatarget="@Html.Raw(globalSetting.DCAProperties["File"])" href="#">@Html.Raw(globalSetting.DCAProperties["File"])</a></li>
|
||||
}
|
||||
@if (globalSetting.IndicatorsProperties.ContainsKey("File")) {
|
||||
<li><a class="btn-edit-presetfile" data-settingname="@Html.Raw(globalSetting.SettingName)" data-datatarget="@Html.Raw(globalSetting.IndicatorsProperties["File"])" href="#">@Html.Raw(globalSetting.IndicatorsProperties["File"])</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<form class="form-horizontal" method="post">
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalEditPresetFile" tabindex="-1" role="dialog" aria-labelledby="modalEditPresetFileTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalEditPresetFileTitle"></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="modalEditPresetFileBody" class="modal-body">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-savepresetfile" data-datatarget="" data-settingname="">Save</button>
|
||||
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
<!-- Code mirror js -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/ace/ace.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
|
||||
$('.btn-edit-presetfile').click(function () {
|
||||
var settingName = $(this).data('settingname');
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
|
||||
$('.btn-savepresetfile').data('settingname', settingName);
|
||||
$('.btn-savepresetfile').data('datatarget', dataTarget);
|
||||
|
||||
$('#modalEditPresetFileTitle').html('Edit ' + dataTarget + ' for ' + settingName);
|
||||
$('#modalEditPresetFile').modal('show');
|
||||
|
||||
$("#modalEditPresetFileBody").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/PresetFile?f=' + encodeURI(dataTarget) + '&gs=' + encodeURI(settingName), '', function (responseText, textStatus, XMLHttpRequest) {
|
||||
if (textStatus == 'error') {
|
||||
$.Notification.notify('error', 'top left', 'Loading preset file failed!', 'PTMagic Monitor failed to load preset file ' + dataTarget + ' for global setting ' + settingName + '.')
|
||||
} else if (responseText == 'returntologin') {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-savepresetfile').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataSetting = $(this).data('settingname');
|
||||
|
||||
var postValues = { FileName: dataTarget, SettingName: dataSetting, FileContent: $('#preset-file').val() };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/SavePresetFile",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(postValues),
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("XSRF-TOKEN",
|
||||
$('input:hidden[name="__RequestVerificationToken"]').val());
|
||||
},
|
||||
success: function (data) {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)PresetFiles?n=PresetFileSaved");
|
||||
},
|
||||
error: function (jqxhr, errorText, thrownError) {
|
||||
$.Notification.notify('error', 'top left', 'Error saving preset file!', 'Error message: ' + errorText);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@if (!Model.NotifyType.Equals("") && !Model.NotifyHeadline.Equals("") && !Model.NotifyMessage.Equals("")) {
|
||||
<text>
|
||||
$.Notification.notify('@Model.NotifyType', 'top left', '@Model.NotifyHeadline', '@Model.NotifyMessage');
|
||||
</text>
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class PresetFilesModel : _Internal.BasePageModelSecure {
|
||||
public List<GlobalSetting> GlobalSettingsWithPresets = new List<GlobalSetting>();
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
public void BindData() {
|
||||
string notification = GetStringParameter("n", "");
|
||||
if (notification.Equals("PresetFileSaved")) {
|
||||
NotifyHeadline = "Preset File Saved!";
|
||||
NotifyMessage = "The preset file was saved and will be applied during the next interval.";
|
||||
NotifyType = "success";
|
||||
}
|
||||
|
||||
foreach (GlobalSetting globalSetting in PTMagicConfiguration.AnalyzerSettings.GlobalSettings) {
|
||||
if (globalSetting.PairsProperties.ContainsKey("File") || globalSetting.DCAProperties.ContainsKey("File") || globalSetting.IndicatorsProperties.ContainsKey("File")) {
|
||||
GlobalSettingsWithPresets.Add(globalSetting);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
@page
|
||||
@model SalesAnalyzer
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/dataTables.bootstrap4.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.bootstrap4.min.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
|
||||
@if (Model.PTData.SellLog.Count == 0) {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">No Sales!</h4>
|
||||
|
||||
<p>Sorry, but your Profit Trailer did not sell anything so far. Please wait for the bot to have at least one sale and you will start seeing data in here.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Sales Analysis</h4>
|
||||
@{
|
||||
double totalProfit = Model.PTData.SellLog.Sum(s => s.Profit);
|
||||
double totalProfitFiat = Math.Round(totalProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double percentGain = Math.Round(totalProfit / Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance * 100, 2);
|
||||
double avgDailyGain = Model.DailyGains.Values.Average(dg => dg);
|
||||
double avgMonthlyGain = Model.MonthlyGains.Values.Average(dg => dg);
|
||||
|
||||
string percentGainText = percentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
if (Model.PTData.TransactionData.Transactions.Count > 0) {
|
||||
percentGainText = "<i class=\"fa fa-info-circle text-muted\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"You have added at least one manual transaction, so the total gain percentage cannot be calculated.\"></i>";
|
||||
}
|
||||
}
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-right">Total</th>
|
||||
<th class="text-right">AVG/Day</th>
|
||||
<th class="text-right">AVG/Month</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Sales</th>
|
||||
<td class="text-right">@Model.PTData.SellLog.Count</td>
|
||||
<td class="text-right">@Math.Round((double)Model.PTData.SellLog.Count / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date).ToList().Count, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@Math.Round((double)Model.PTData.SellLog.Count / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date.ToString("yyyy-MM")).ToList().Count, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Profit</th>
|
||||
<td class="text-right text-autocolor">@totalProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Math.Round(totalProfit / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date).ToList().Count, 8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Math.Round(totalProfit / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date.ToString("yyyy-MM")).ToList().Count, 8).ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Profit USD</th>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + totalProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(totalProfitFiat / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date).ToList().Count, 8).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(totalProfitFiat / (double)Model.PTData.SellLog.GroupBy(d => d.SoldDate.Date.ToString("yyyy-MM")).ToList().Count, 8).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>% Gain</th>
|
||||
<td class="text-right text-autocolor">@Html.Raw(percentGainText)</td>
|
||||
<td class="text-right text-autocolor">@avgDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
<td class="text-right text-autocolor">@avgMonthlyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
@{
|
||||
double currentTotalBalance = Model.PTData.GetCurrentBalance();
|
||||
double estimatedBalance1Month = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 30.0), 8);
|
||||
double estimatedBalance3Months = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 90.0), 8);
|
||||
double estimatedBalance6Months = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 180.0), 8);
|
||||
double estimatedBalance1Year = Math.Round(currentTotalBalance * Math.Pow((1 + (avgDailyGain / 100)), 365.0), 8);
|
||||
}
|
||||
|
||||
<h4 class="m-t-0 header-title">Balance Prediction <small><i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The balance prediction is based on your daily average gain of @avgDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))% and your current approximate balance of @currentTotalBalance.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))"></i></small></h4>
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-right"></th>
|
||||
<th class="text-right">Est. Balance</th>
|
||||
<th class="text-right">Est. @Model.Summary.MainFiatCurrency Value</th>
|
||||
<th class="text-right">Est. Gain</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>1 month</th>
|
||||
<td class="text-right text-autocolor">@estimatedBalance1Month.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance1Month * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Month - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>3 months</th>
|
||||
<td class="text-right text-autocolor">@estimatedBalance3Months.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance3Months * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Math.Round((estimatedBalance3Months - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>6 months</th>
|
||||
<td class="text-right text-autocolor">@estimatedBalance6Months.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance6Months * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Math.Round((estimatedBalance6Months - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>1 year</th>
|
||||
<td class="text-right text-autocolor">@estimatedBalance1Year.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + Math.Round(estimatedBalance1Year * Model.Summary.MainMarketPrice, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Math.Round((estimatedBalance1Year - currentTotalBalance) / currentTotalBalance * 100, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<div class="trades-chart">
|
||||
<svg style="height:220px;width:100%"></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<div class="profit-chart">
|
||||
<svg style="height:220px;width:100%"></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Last @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries days</h4>
|
||||
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Day</th>
|
||||
<th class="text-right">Sales</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainMarket</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
|
||||
<th class="text-right">% Gain</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (DateTime salesDate = Model.DateTimeNow.DateTime.Date; salesDate >= Model.DateTimeNow.DateTime.AddDays(-Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries) && salesDate >= Model.MinSellLogDate; salesDate = salesDate.AddDays(-1)) {
|
||||
List<Core.Main.DataObjects.PTMagicData.SellLogData> salesDateSales = Model.PTData.SellLog.FindAll(sl => sl.SoldDate.Date == salesDate);
|
||||
double salesDateProfit = salesDateSales.Sum(sl => sl.Profit);
|
||||
double salesDateProfitFiat = Math.Round(salesDateProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double salesDateStartBalance = Model.PTData.GetSnapshotBalance(salesDate);
|
||||
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
|
||||
<tr>
|
||||
<td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/SalesList/?d=@salesDate.ToString("yyyy-MM-dd")" data-remote="false" data-toggle="modal" data-target="#salesList">@salesDate.ToShortDateString()</a></td>
|
||||
<td class="text-right">@salesDateSales.Count</td>
|
||||
<td class="text-right text-autocolor">@salesDateProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + salesDateProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@salesDateGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Last @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries months</h4>
|
||||
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month</th>
|
||||
<th class="text-right">Sales</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainMarket</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
|
||||
<th class="text-right">% Gain</th>
|
||||
<th class="text-right">AVG %/Day</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{
|
||||
DateTime minSellLogMonthDate = new DateTime(Model.MinSellLogDate.Year, Model.MinSellLogDate.Month, 1).Date;
|
||||
DateTime salesMonthStartDate = new DateTime(Model.DateTimeNow.DateTime.Year, Model.DateTimeNow.DateTime.Month, 1).Date;
|
||||
}
|
||||
@for (DateTime salesMonthDate = salesMonthStartDate.Date; salesMonthDate >= Model.DateTimeNow.DateTime.AddMonths(-Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries) && salesMonthDate >= minSellLogMonthDate; salesMonthDate = salesMonthDate.AddMonths(-1)) {
|
||||
List<Core.Main.DataObjects.PTMagicData.SellLogData> salesMonthSales = Model.PTData.SellLog.FindAll(sl => sl.SoldDate.Date.Month == salesMonthDate.Month && sl.SoldDate.Date.Year == salesMonthDate.Year);
|
||||
double salesDateProfit = salesMonthSales.Sum(sl => sl.Profit);
|
||||
double salesDateProfitFiat = Math.Round(salesDateProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double salesDateStartBalance = Model.PTData.GetSnapshotBalance(salesMonthDate);
|
||||
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
|
||||
|
||||
double salesDateAVGDailyGain = 0;
|
||||
double monthDailyProfit = 0;
|
||||
int days = 0;
|
||||
for (int d = 1; d <= DateTime.DaysInMonth(salesMonthDate.Year, salesMonthDate.Month); d++) {
|
||||
DateTime monthDay = salesMonthDate.AddDays(-salesMonthDate.Day + d);
|
||||
if (monthDay <= Model.DateTimeNow) {
|
||||
days++;
|
||||
List<Core.Main.DataObjects.PTMagicData.SellLogData> monthDaySales = Model.PTData.SellLog.FindAll(sl => sl.SoldDate.Date == monthDay.Date);
|
||||
double monthDayProfit = monthDaySales.Sum(sl => sl.Profit);
|
||||
double monthDayStartBalance = Model.PTData.GetSnapshotBalance(monthDay);
|
||||
monthDailyProfit += Math.Round(monthDayProfit / monthDayStartBalance * 100, 2);
|
||||
}
|
||||
}
|
||||
salesDateAVGDailyGain = Math.Round(monthDailyProfit / days, 2);
|
||||
<tr>
|
||||
<td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/SalesList/?m=@salesMonthDate.ToString("yyyy-MM")" data-remote="false" data-toggle="modal" data-target="#salesList">@salesMonthDate.ToString("MMMM", new System.Globalization.CultureInfo("en-US"))</a></td>
|
||||
<td class="text-right text-autocolor">@salesMonthSales.Count</td>
|
||||
<td class="text-right text-autocolor">@salesDateProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + salesDateProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@salesDateGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
<td class="text-right text-autocolor">@salesDateAVGDailyGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="card-box">
|
||||
|
||||
<h4 class="m-t-0 header-title"><b>Top @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets Sales Market Analysis</b></h4>
|
||||
|
||||
<table class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-tablesaw-priority="persist" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Rank</th>
|
||||
<th scope="col" data-tablesaw-sortable-col>Market</th>
|
||||
<th scope="col" class="text-right" data-tablesaw-sortable-col>Sales</th>
|
||||
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.Summary.MainMarket</th>
|
||||
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.Summary.MainFiatCurrency</th>
|
||||
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit @Model.Summary.MainFiatCurrency/Trade</th>
|
||||
<th scope="col" class="text-right" data-tablesaw-sortable-col>Profit %/Trade</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{
|
||||
var topMarkets = Model.PTData.SellLog.GroupBy(m => m.Market).Select(mg => mg.Sum(m => m.Profit));
|
||||
int marketRank = 0;
|
||||
}
|
||||
@foreach (KeyValuePair<string, double> marketData in Model.TopMarkets) {
|
||||
marketRank++;
|
||||
int trades = Model.PTData.SellLog.FindAll(m => m.Market == marketData.Key).Count;
|
||||
double profitFiat = Math.Round(marketData.Value * Model.Summary.MainMarketPrice, 2);
|
||||
double profitFiatPerTrade = Math.Round(profitFiat / trades, 2);
|
||||
<tr>
|
||||
<td>@marketRank</td>
|
||||
<td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform, Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, marketData.Key, Model.Summary.MainMarket)" target="_blank">@marketData.Key</a></td>
|
||||
<td class="text-right">@trades</td>
|
||||
<td class="text-right text-autocolor-saw">@marketData.Value.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor-saw">@profitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainFiatCurrency</td>
|
||||
<td class="text-right text-autocolor-saw">@profitFiatPerTrade.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) @Model.Summary.MainFiatCurrency</td>
|
||||
<td class="text-right text-autocolor-saw">@Model.PTData.SellLog.FindAll(m => m.Market == marketData.Key).Average(p => p.ProfitPercent).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="salesList" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog modal-full">
|
||||
<div class="modal-content">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/d3/d3.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.js"></script>
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
|
||||
|
||||
<!-- Required datatable js -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/jquery.dataTables.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/dataTables.bootstrap4.min.js"></script>
|
||||
<!-- Buttons examples -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/dataTables.buttons.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.bootstrap4.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/jszip.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/pdfmake.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/vfs_fonts.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.html5.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.print.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/datatables/buttons.colVis.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function ($) {
|
||||
'use strict';
|
||||
nv.addGraph(function () {
|
||||
var lineChart = nv.models.lineChart();
|
||||
var height = 300;
|
||||
/**/
|
||||
var chartData = @Html.Raw(Model.TradesChartDataJSON);
|
||||
/**/
|
||||
lineChart.useInteractiveGuideline(true);
|
||||
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
|
||||
lineChart.yAxis.axisLabel('Sales').tickFormat(d3.format(','));
|
||||
d3.select('.trades-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
|
||||
nv.utils.windowResize(lineChart.update);
|
||||
|
||||
return lineChart;
|
||||
});
|
||||
|
||||
nv.addGraph(function () {
|
||||
var lineChart = nv.models.lineChart();
|
||||
var height = 300;
|
||||
/**/
|
||||
var chartData = @Html.Raw(Model.ProfitChartDataJSON);
|
||||
/**/
|
||||
lineChart.useInteractiveGuideline(true);
|
||||
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
|
||||
lineChart.yAxis.axisLabel('Profit').tickFormat(d3.format(',.2f'));
|
||||
d3.select('.profit-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
|
||||
nv.utils.windowResize(lineChart.update);
|
||||
|
||||
return lineChart;
|
||||
});
|
||||
|
||||
$("#salesList").on("show.bs.modal", function (e) {
|
||||
$(this).find(".modal-content").html('<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>');
|
||||
var link = $(e.relatedTarget);
|
||||
$(this).find(".modal-content").load(link.attr("href"), function () {
|
||||
$('.text-autocolor').autocolor(false);
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
||||
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SalesAnalyzer : _Internal.BasePageModelSecure {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public string TradesChartDataJSON = "";
|
||||
public string ProfitChartDataJSON = "";
|
||||
public IEnumerable<KeyValuePair<string, double>> TopMarkets = null;
|
||||
public DateTime MinSellLogDate = Constants.confMinDate;
|
||||
public Dictionary<DateTime, double> DailyGains = new Dictionary<DateTime, double>();
|
||||
public Dictionary<DateTime, double> MonthlyGains = new Dictionary<DateTime, double>();
|
||||
public DateTimeOffset DateTimeNow = Constants.confMinDate;
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
|
||||
// Convert local offset time to UTC
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
|
||||
|
||||
BuildTopMarkets();
|
||||
BuildSalesChartData();
|
||||
}
|
||||
|
||||
private void BuildTopMarkets() {
|
||||
var markets = PTData.SellLog.GroupBy(m => m.Market);
|
||||
Dictionary<string, double> topMarketsDic = new Dictionary<string, double>();
|
||||
foreach (var market in markets) {
|
||||
double totalProfit = PTData.SellLog.FindAll(m => m.Market == market.Key).Sum(m => m.Profit);
|
||||
|
||||
topMarketsDic.Add(market.Key, totalProfit);
|
||||
}
|
||||
TopMarkets = new SortedDictionary<string, double>(topMarketsDic).OrderByDescending(m => m.Value).Take(PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets);
|
||||
}
|
||||
|
||||
private void BuildSalesChartData() {
|
||||
if (PTData.SellLog.Count > 0) {
|
||||
MinSellLogDate = PTData.SellLog.OrderBy(sl => sl.SoldDate).First().SoldDate.Date;
|
||||
DateTime graphStartDate = DateTimeNow.DateTime.Date.AddDays(-30);
|
||||
if (MinSellLogDate > graphStartDate) graphStartDate = MinSellLogDate;
|
||||
|
||||
int tradeDayIndex = 0;
|
||||
string tradesPerDayJSON = "";
|
||||
string profitPerDayJSON = "";
|
||||
for (DateTime salesDate = graphStartDate; salesDate <= DateTimeNow.DateTime.Date; salesDate = salesDate.AddDays(1)) {
|
||||
if (tradeDayIndex > 0) {
|
||||
tradesPerDayJSON += ",\n";
|
||||
profitPerDayJSON += ",\n";
|
||||
}
|
||||
|
||||
int trades = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate.Date).Count;
|
||||
double profit = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate.Date).Sum(t => t.Profit);
|
||||
double profitFiat = Math.Round(profit * Summary.MainMarketPrice, 2);
|
||||
|
||||
tradesPerDayJSON += "{x: new Date('" + salesDate.Date.ToString("yyyy-MM-dd") + "'), y: " + trades + "}";
|
||||
profitPerDayJSON += "{x: new Date('" + salesDate.Date.ToString("yyyy-MM-dd") + "'), y: " + profitFiat.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
|
||||
|
||||
tradeDayIndex++;
|
||||
}
|
||||
|
||||
TradesChartDataJSON = "[";
|
||||
TradesChartDataJSON += "{";
|
||||
TradesChartDataJSON += "key: 'Sales',";
|
||||
TradesChartDataJSON += "color: '" + Constants.ChartLineColors[0] + "',";
|
||||
TradesChartDataJSON += "values: [" + tradesPerDayJSON + "]";
|
||||
TradesChartDataJSON += "}";
|
||||
TradesChartDataJSON += "]";
|
||||
|
||||
ProfitChartDataJSON = "[";
|
||||
ProfitChartDataJSON += "{";
|
||||
ProfitChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
|
||||
ProfitChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
|
||||
ProfitChartDataJSON += "values: [" + profitPerDayJSON + "]";
|
||||
ProfitChartDataJSON += "}";
|
||||
ProfitChartDataJSON += "]";
|
||||
|
||||
for (DateTime salesDate = DateTimeNow.DateTime.Date; salesDate >= MinSellLogDate; salesDate = salesDate.AddDays(-1)) {
|
||||
List<SellLogData> salesDateSales = PTData.SellLog.FindAll(sl => sl.SoldDate.Date == salesDate);
|
||||
double salesDateProfit = salesDateSales.Sum(sl => sl.Profit);
|
||||
double salesDateStartBalance = PTData.GetSnapshotBalance(salesDate);
|
||||
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
|
||||
|
||||
DailyGains.Add(salesDate, salesDateGain);
|
||||
}
|
||||
|
||||
DateTime minSellLogMonthDate = new DateTime(MinSellLogDate.Year, MinSellLogDate.Month, 1).Date;
|
||||
DateTime salesMonthStartDate = new DateTime(DateTimeNow.DateTime.Year, DateTimeNow.DateTime.Month, 1).Date;
|
||||
for (DateTime salesMonthDate = salesMonthStartDate.Date; salesMonthDate >= minSellLogMonthDate; salesMonthDate = salesMonthDate.AddMonths(-1)) {
|
||||
List<Core.Main.DataObjects.PTMagicData.SellLogData> salesMonthSales = PTData.SellLog.FindAll(sl => sl.SoldDate.Date.Month == salesMonthDate.Month && sl.SoldDate.Date.Year == salesMonthDate.Year);
|
||||
double salesDateProfit = salesMonthSales.Sum(sl => sl.Profit);
|
||||
double salesDateStartBalance = PTData.GetSnapshotBalance(salesMonthDate);
|
||||
double salesDateGain = Math.Round(salesDateProfit / salesDateStartBalance * 100, 2);
|
||||
|
||||
MonthlyGains.Add(salesMonthDate, salesDateGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
@page
|
||||
@model SettingsAnalyzerModel
|
||||
@{ ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/switchery/css/switchery.min.css" rel="stylesheet" />
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Analyzer Settings<span></span></h4>
|
||||
|
||||
<p>
|
||||
In this area you may change the settings of your <b>settings.analyzer.json</b> file. Please use this with caution as all settings you saved here will directly affect your PT Magic bot.
|
||||
</p>
|
||||
<p class="text-danger">Please note: When you save your settings using this interface, all existing comments will be removed from your settings.analyzer.json!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Restore Backup <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="PT Magic automatically creates a backup when you save your settings.analyzer.json file using this interface. In case you notice some suspicious behaviour or just want to go back to your previous settings, you may restore the most recent backup here."></i></h4>
|
||||
|
||||
<p>
|
||||
You messed up your settings?<br />Restore your backup!
|
||||
</p>
|
||||
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light" data-toggle="modal" data-target="#modalRestoreBackup" type="submit">
|
||||
Restore Backup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Download Settings</h4>
|
||||
|
||||
<p>
|
||||
Want to save or share your settings?<br />Download them!
|
||||
</p>
|
||||
|
||||
<a class="btn btn-ptmagic text-uppercase waves-effect waves-light" href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/DownloadFile?f=settings.analyzer.json">
|
||||
Download Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Upload Settings</h4>
|
||||
|
||||
<p>
|
||||
Found some settings somewhere?<br />Upload them!
|
||||
</p>
|
||||
|
||||
<button class="btn btn-ptmagic btn-custom text-uppercase waves-effect waves-light">
|
||||
Coming soon...
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h4 class="m-t-0 header-title">Market Trends (@Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Count)</h4>
|
||||
|
||||
<ul>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend mt in Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends) {
|
||||
<li><a href="#MarketTrend_@Core.Helper.SystemHelper.StripBadCode(mt.Name, Core.Main.Constants.WhiteListNames)">@Core.Helper.SystemHelper.SplitCamelCase(mt.Name)</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="m-t-0 header-title">Global Settings (@Model.PTMagicConfiguration.AnalyzerSettings.GlobalSettings.Count)</h4>
|
||||
|
||||
<ul>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.GlobalSetting gs in Model.PTMagicConfiguration.AnalyzerSettings.GlobalSettings) {
|
||||
<li><a href="#GlobalSetting_@Core.Helper.SystemHelper.StripBadCode(gs.SettingName, Core.Main.Constants.WhiteListNames)">@Core.Helper.SystemHelper.SplitCamelCase(gs.SettingName)</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="m-t-0 header-title">Single Market Settings (@Model.PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings.Count)</h4>
|
||||
|
||||
<ul>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.SingleMarketSetting sms in Model.PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings) {
|
||||
<li><a href="#SingleMarketSetting_@Core.Helper.SystemHelper.StripBadCode(sms.SettingName, Core.Main.Constants.WhiteListNames)">@Core.Helper.SystemHelper.SplitCamelCase(sms.SettingName)</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<button class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light btn-new" data-datatype="MarketTrend">
|
||||
Add Market Trend
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group btn-block">
|
||||
<button class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light dropdown-toggle" data-toggle="dropdown">
|
||||
Add Global Setting
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.GlobalSetting gs in Model.PTMagicConfiguration.AnalyzerSettings.GlobalSettings) {
|
||||
<a class="dropdown-item btn-new" data-datatype="GlobalSetting" data-datatarget="@Core.Helper.SystemHelper.StripBadCode(gs.SettingName, Core.Main.Constants.WhiteListNames)">Add before <i>@Core.Helper.SystemHelper.SplitCamelCase(gs.SettingName)</i></a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group btn-block">
|
||||
<button class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light dropdown-toggle" data-toggle="dropdown">
|
||||
Add Single Market Setting
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.SingleMarketSetting sms in Model.PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings) {
|
||||
<a class="dropdown-item btn-new" data-datatype="SingleMarketSetting" data-datadirection="before" data-datatarget="@Core.Helper.SystemHelper.StripBadCode(sms.SettingName, Core.Main.Constants.WhiteListNames)">Add before <i>@Core.Helper.SystemHelper.SplitCamelCase(sms.SettingName)</i></a>
|
||||
}
|
||||
<div class="dropdown-divider"></div>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.SingleMarketSetting sms in Model.PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings) {
|
||||
<a class="dropdown-item btn-new" data-datatype="SingleMarketSetting" data-datadirection="after" data-datatarget="@Core.Helper.SystemHelper.StripBadCode(sms.SettingName, Core.Main.Constants.WhiteListNames)">Add after <i>@Core.Helper.SystemHelper.SplitCamelCase(sms.SettingName)</i></a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal" method="post">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Market Analyzer</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Store Data Max Hours <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Number of hours to store market data."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_StoreDataMaxHours" value="@Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours.ToString()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Interval Minutes <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Interval in minutes for PTMagic to check market trends and triggers."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_IntervalMinutes" value="@Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes.ToString()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Exclude Main Currency <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Excludes the main currency (for example BTC) from market trend analysis."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="MarketAnalyzer_ExcludeMainCurrency" checked="@(Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.ExcludeMainCurrency)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="MarketAnalyzer_MarketTrends">
|
||||
@if (Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Count > 0) {
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.MarketTrend mt in Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends) {
|
||||
<div class="settings-markettrend" data-trendname="@Core.Helper.SystemHelper.StripBadCode(mt.Name, Core.Main.Constants.WhiteListNames)" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>
|
||||
}
|
||||
} else {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
No Market Trends found!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="MarketAnalyzer_GlobalSettings">
|
||||
@if (Model.PTMagicConfiguration.AnalyzerSettings.GlobalSettings.Count > 0) {
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.GlobalSetting gs in Model.PTMagicConfiguration.AnalyzerSettings.GlobalSettings) {
|
||||
<div class="settings-globalsetting" data-settingname="@Core.Helper.SystemHelper.StripBadCode(gs.SettingName, Core.Main.Constants.WhiteListNames)" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>
|
||||
}
|
||||
} else {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
No Global Settings found!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="MarketAnalyzer_SingleMarketSettings">
|
||||
@if (Model.PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings.Count > 0) {
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.SingleMarketSetting sms in Model.PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings) {
|
||||
<div class="settings-singlemarketsetting" data-settingname="@Core.Helper.SystemHelper.StripBadCode(sms.SettingName, Core.Main.Constants.WhiteListNames)" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>
|
||||
}
|
||||
} else {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
No Single Market Settings found!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="div-loading-settings" class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<span class="btn btn-danger btn-custom btn-block text-uppercase waves-effect waves-light">
|
||||
<i class="fa fa-circle-o-notch fa-spin fa-fw"></i> Loading settings, please wait...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="div-save-settings" class="row hidden">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<a class="btn btn-ptmagic btn-block text-uppercase waves-effect waves-light" data-toggle="modal" data-target="#modalSaveSettings" href="#">
|
||||
Save Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalRestoreBackup" tabindex="-1" role="dialog" aria-labelledby="modalRestoreBackupTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalRestoreBackupTitle">Are you sure?</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do you really want to restore a backup of your settings.analyzer.json and overwrite all current settings?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-restorebackup" data-datatarget="settings.analyzer.json">Yes, do it!</button>
|
||||
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">No...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalSaveSettings" tabindex="-1" role="dialog" aria-labelledby="modalSaveSettingsTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalSaveSettingsTitle">Are you sure?</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do you really want to save the current settings?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-savesettings">Yes, do it!</button>
|
||||
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">No...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/switchery/js/switchery.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/analyzer-settings.js?v=@Html.Raw(Model.Summary.Version)"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
$('.settings-markettrend').buildMarketTrendSettings();
|
||||
$('.settings-globalsetting').buildGlobalSettings();
|
||||
$('.settings-singlemarketsetting').buildSingleMarketSettings();
|
||||
|
||||
$('.btn-new').click(function () {
|
||||
var dataType = $(this).data('datatype');
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataDirection = $(this).data('datadirection');
|
||||
|
||||
switch (dataType) {
|
||||
case 'MarketTrend':
|
||||
$('#MarketAnalyzer_MarketTrends').append('<div class="settings-markettrend new" data-trendname="" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>');
|
||||
$('html, body').scrollTop($('#MarketAnalyzer_MarketTrends').offset().top + $('#MarketAnalyzer_MarketTrends').height() - 100);
|
||||
$('.settings-markettrend.new').buildMarketTrendSettings();
|
||||
break;
|
||||
case 'GlobalSetting':
|
||||
$('html, body').scrollTop($('#MarketAnalyzer_GlobalSettings > [data-settingname="' + dataTarget + '"]').offset().top - 100);
|
||||
$('#MarketAnalyzer_GlobalSettings > [data-settingname="' + dataTarget + '"]').before('<div class="settings-globalsetting new" data-settingname="" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>');
|
||||
$('.settings-globalsetting.new').buildGlobalSettings();
|
||||
break;
|
||||
case 'SingleMarketSetting':
|
||||
if (dataDirection === 'before') {
|
||||
$('html, body').scrollTop($('#MarketAnalyzer_SingleMarketSettings > [data-settingname="' + dataTarget + '"]').offset().top - 100);
|
||||
$('#MarketAnalyzer_SingleMarketSettings > [data-settingname="' + dataTarget + '"]').before('<div class="settings-singlemarketsetting new" data-settingname="" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>');
|
||||
} else {
|
||||
$('html, body').scrollTop($('#MarketAnalyzer_SingleMarketSettings > [data-settingname="' + dataTarget + '"]').offset().top + $('#MarketAnalyzer_SingleMarketSettings > [data-settingname="' + dataTarget + '"]').height() - 100);
|
||||
$('#MarketAnalyzer_SingleMarketSettings > [data-settingname="' + dataTarget + '"]').after('<div class="settings-singlemarketsetting new" data-settingname="" data-rooturl="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i></div>');
|
||||
}
|
||||
$('.settings-singlemarketsetting.new').buildSingleMarketSettings();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-savesettings', function () {
|
||||
$('form').submit();
|
||||
});
|
||||
|
||||
$('.btn-restorebackup').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
|
||||
var postValues = { File: dataTarget };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/RestoreBackup",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(postValues),
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("XSRF-TOKEN",
|
||||
$('input:hidden[name="__RequestVerificationToken"]').val());
|
||||
},
|
||||
success: function (data) {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsAnalyzer?n=BackupRestored");
|
||||
},
|
||||
error: function (jqxhr, errorText, thrownError) {
|
||||
$.Notification.notify('error', 'top left', 'Error restoring backup!', 'Error message: ' + errorText);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@if (!Model.NotifyType.Equals("") && !Model.NotifyHeadline.Equals("") && !Model.NotifyMessage.Equals("")) {
|
||||
<text>
|
||||
$.Notification.notify('@Model.NotifyType', 'top left', '@Model.NotifyHeadline', '@Model.NotifyMessage');
|
||||
window.history.pushState('@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor', '@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor', '@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsAnalyzer');
|
||||
</text>
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SettingsAnalyzerModel : _Internal.BasePageModelSecure {
|
||||
public string ValidationMessage = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
string notification = GetStringParameter("n", "");
|
||||
if (notification.Equals("BackupRestored")) {
|
||||
NotifyHeadline = "Backup restored!";
|
||||
NotifyMessage = "Your backup of settings.analyzer.json was successfully restored.";
|
||||
NotifyType = "success";
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
base.Init();
|
||||
|
||||
PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours = SystemHelper.TextToInteger(HttpContext.Request.Form["MarketAnalyzer_StoreDataMaxHours"], PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.StoreDataMaxHours);
|
||||
PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["MarketAnalyzer_IntervalMinutes"], PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes);
|
||||
PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.ExcludeMainCurrency = HttpContext.Request.Form["MarketAnalyzer_ExcludeMainCurrency"].Equals("on");
|
||||
|
||||
List<string> formKeys = HttpContext.Request.Form.Keys.ToList();
|
||||
|
||||
SaveMarketTrends(formKeys);
|
||||
SaveGlobalSettings(formKeys);
|
||||
SaveSingleMarketSettings(formKeys);
|
||||
|
||||
PTMagicConfiguration.WriteAnalyzerSettings(PTMagicBasePath);
|
||||
|
||||
NotifyHeadline = "Settings saved!";
|
||||
NotifyMessage = "Settings saved successfully to settings.analyzer.json.";
|
||||
NotifyType = "success";
|
||||
}
|
||||
|
||||
private void SaveMarketTrends(List<string> formKeys) {
|
||||
List<MarketTrend> newMarketTrends = new List<MarketTrend>();
|
||||
List<string> marketTrendFormKeys = formKeys.FindAll(k => k.StartsWith("MarketAnalyzer_MarketTrend_") && k.EndsWith("|Name"));
|
||||
foreach (string marketTrendFormKey in marketTrendFormKeys) {
|
||||
MarketTrend mt = null;
|
||||
|
||||
string originalNameSimplified = marketTrendFormKey.Replace("MarketAnalyzer_MarketTrend_", "").Replace("|Name", "");
|
||||
string mtFormKey = "MarketAnalyzer_MarketTrend_" + originalNameSimplified + "|";
|
||||
|
||||
if (originalNameSimplified.Equals("")) {
|
||||
mt = new MarketTrend();
|
||||
} else {
|
||||
mt = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(m => SystemHelper.StripBadCode(m.Name, Constants.WhiteListNames).Equals(originalNameSimplified));
|
||||
}
|
||||
|
||||
mt.Name = HttpContext.Request.Form[marketTrendFormKey];
|
||||
mt.Platform = HttpContext.Request.Form[mtFormKey + "Platform"];
|
||||
mt.MaxMarkets = SystemHelper.TextToInteger(HttpContext.Request.Form[mtFormKey + "MaxMarkets"], mt.MaxMarkets);
|
||||
mt.TrendMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form[mtFormKey + "TrendMinutes"], mt.TrendMinutes);
|
||||
mt.TrendCurrency = HttpContext.Request.Form[mtFormKey + "TrendCurrency"];
|
||||
mt.IgnoredMarkets = HttpContext.Request.Form[mtFormKey + "IgnoredMarkets"];
|
||||
mt.AllowedMarkets = HttpContext.Request.Form[mtFormKey + "AllowedMarkets"];
|
||||
mt.DisplayGraph = HttpContext.Request.Form[mtFormKey + "DisplayGraph"].Equals("on");
|
||||
mt.ExcludeMainCurrency = HttpContext.Request.Form[mtFormKey + "ExcludeMainCurrency"].Equals("on");
|
||||
|
||||
newMarketTrends.Add(mt);
|
||||
}
|
||||
PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends = newMarketTrends;
|
||||
}
|
||||
|
||||
private void SaveGlobalSettings(List<string> formKeys) {
|
||||
List<GlobalSetting> newGlobalMarketSettings = new List<GlobalSetting>();
|
||||
List<string> globalSettingFormKeys = formKeys.FindAll(k => k.StartsWith("MarketAnalyzer_GlobalSetting_") && k.EndsWith("|SettingName"));
|
||||
foreach (string globalSettingFormKey in globalSettingFormKeys) {
|
||||
GlobalSetting gs = null;
|
||||
|
||||
string originalNameSimplified = globalSettingFormKey.Replace("MarketAnalyzer_GlobalSetting_", "").Replace("|SettingName", "");
|
||||
string gsFormKey = "MarketAnalyzer_GlobalSetting_" + originalNameSimplified + "|";
|
||||
|
||||
if (originalNameSimplified.Equals("")) {
|
||||
gs = new GlobalSetting();
|
||||
} else {
|
||||
gs = PTMagicConfiguration.AnalyzerSettings.GlobalSettings.Find(s => SystemHelper.StripBadCode(s.SettingName, Constants.WhiteListNames).Equals(originalNameSimplified));
|
||||
}
|
||||
|
||||
gs.SettingName = SystemHelper.StripBadCode(HttpContext.Request.Form[gsFormKey + "SettingName"], Constants.WhiteListNames);
|
||||
gs.TriggerConnection = HttpContext.Request.Form[gsFormKey + "TriggerConnection"];
|
||||
|
||||
// Triggers
|
||||
if (!gs.SettingName.Equals("Default", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
List<Trigger> newTriggers = new List<Trigger>();
|
||||
List<string> globalSettingTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(gsFormKey + "Trigger_") && k.EndsWith("|MarketTrendName"));
|
||||
foreach (string globalSettingTriggerFormKey in globalSettingTriggerFormKeys) {
|
||||
Trigger trigger = null;
|
||||
|
||||
string originalTriggerNameSimplified = globalSettingTriggerFormKey.Replace(gsFormKey + "Trigger_", "").Replace("|MarketTrendName", "");
|
||||
string tFormKey = gsFormKey + "Trigger_" + originalTriggerNameSimplified + "|";
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey + "MarketTrendName"].Count; f++) {
|
||||
|
||||
if (originalTriggerNameSimplified.Equals("")) {
|
||||
trigger = new Trigger();
|
||||
} else {
|
||||
trigger = gs.Triggers.Find(t => SystemHelper.StripBadCode(t.MarketTrendName, Constants.WhiteListNames).Equals(originalTriggerNameSimplified));
|
||||
}
|
||||
|
||||
trigger.MarketTrendName = HttpContext.Request.Form[tFormKey + "MarketTrendName"][f];
|
||||
trigger.MinChange = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "MinChange"][f], Constants.MinTrendChange, "en-US");
|
||||
trigger.MaxChange = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "MaxChange"][f], Constants.MaxTrendChange, "en-US");
|
||||
|
||||
newTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
gs.Triggers = newTriggers;
|
||||
}
|
||||
|
||||
// Pairs Properties
|
||||
Dictionary<string, object> newPairsProperties = GetProfitTrailerProperties(formKeys, gsFormKey, "Pairs");
|
||||
gs.PairsProperties = newPairsProperties;
|
||||
|
||||
// DCA Properties
|
||||
Dictionary<string, object> newDCAProperties = GetProfitTrailerProperties(formKeys, gsFormKey, "DCA");
|
||||
gs.DCAProperties = newDCAProperties;
|
||||
|
||||
// Indicators Properties
|
||||
Dictionary<string, object> newIndicatorsProperties = GetProfitTrailerProperties(formKeys, gsFormKey, "Indicators");
|
||||
gs.IndicatorsProperties = newIndicatorsProperties;
|
||||
|
||||
newGlobalMarketSettings.Add(gs);
|
||||
}
|
||||
PTMagicConfiguration.AnalyzerSettings.GlobalSettings = newGlobalMarketSettings;
|
||||
}
|
||||
|
||||
private void SaveSingleMarketSettings(List<string> formKeys) {
|
||||
List<SingleMarketSetting> newSingleMarketMarketSettings = new List<SingleMarketSetting>();
|
||||
List<string> singleMarketSettingFormKeys = formKeys.FindAll(k => k.StartsWith("MarketAnalyzer_SingleMarketSetting_") && k.EndsWith("|SettingName"));
|
||||
foreach (string singleMarketSettingFormKey in singleMarketSettingFormKeys) {
|
||||
SingleMarketSetting sms = null;
|
||||
|
||||
string originalNameSimplified = singleMarketSettingFormKey.Replace("MarketAnalyzer_SingleMarketSetting_", "").Replace("|SettingName", "");
|
||||
string smsFormKey = "MarketAnalyzer_SingleMarketSetting_" + originalNameSimplified + "|";
|
||||
|
||||
if (originalNameSimplified.Equals("")) {
|
||||
sms = new SingleMarketSetting();
|
||||
} else {
|
||||
sms = PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings.Find(s => SystemHelper.StripBadCode(s.SettingName, Constants.WhiteListNames).Equals(originalNameSimplified));
|
||||
}
|
||||
|
||||
sms.SettingName = SystemHelper.StripBadCode(HttpContext.Request.Form[smsFormKey + "SettingName"], Constants.WhiteListNames);
|
||||
sms.TriggerConnection = HttpContext.Request.Form[smsFormKey + "TriggerConnection"];
|
||||
sms.OffTriggerConnection = HttpContext.Request.Form[smsFormKey + "OffTriggerConnection"];
|
||||
sms.IgnoredMarkets = HttpContext.Request.Form[smsFormKey + "IgnoredMarkets"];
|
||||
sms.AllowedMarkets = HttpContext.Request.Form[smsFormKey + "AllowedMarkets"];
|
||||
sms.StopProcessWhenTriggered = HttpContext.Request.Form[smsFormKey + "StopProcessWhenTriggered"].Equals("on");
|
||||
|
||||
#region Triggers
|
||||
List<Trigger> newTriggers = new List<Trigger>();
|
||||
List<string> singleMarketSettingTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(smsFormKey + "Trigger_") && k.EndsWith("|MarketTrendName"));
|
||||
foreach (string singleMarketSettingTriggerFormKey in singleMarketSettingTriggerFormKeys) {
|
||||
Trigger trigger = null;
|
||||
|
||||
string originalTriggerNameSimplified = singleMarketSettingTriggerFormKey.Replace(smsFormKey + "Trigger_", "").Replace("|MarketTrendName", "");
|
||||
string tFormKey = smsFormKey + "Trigger_" + originalTriggerNameSimplified + "|";
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey + "MarketTrendName"].Count; f++) {
|
||||
if (originalTriggerNameSimplified.Equals("")) {
|
||||
trigger = new Trigger();
|
||||
} else {
|
||||
trigger = sms.Triggers.Find(t => SystemHelper.StripBadCode(t.MarketTrendName, Constants.WhiteListNames).Equals(originalTriggerNameSimplified));
|
||||
}
|
||||
|
||||
trigger.MarketTrendName = HttpContext.Request.Form[tFormKey + "MarketTrendName"][f];
|
||||
trigger.MarketTrendRelation = HttpContext.Request.Form[tFormKey + "MarketTrendRelation"][f];
|
||||
trigger.MinChange = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "MinChange"][f], Constants.MinTrendChange, "en-US");
|
||||
trigger.MaxChange = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "MaxChange"][f], Constants.MaxTrendChange, "en-US");
|
||||
|
||||
newTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> singleMarketSettingCoinAgeTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(smsFormKey + "Trigger_AgeDaysLowerThan"));
|
||||
foreach (string singleMarketSettingCoinAgeTriggerFormKey in singleMarketSettingCoinAgeTriggerFormKeys) {
|
||||
Trigger trigger = null;
|
||||
|
||||
string originalTriggerIndex = singleMarketSettingCoinAgeTriggerFormKey.Replace(smsFormKey + "Trigger_AgeDaysLowerThan", "");
|
||||
string tFormKey = smsFormKey + "Trigger_AgeDaysLowerThan" + originalTriggerIndex;
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey].Count; f++) {
|
||||
trigger = new Trigger();
|
||||
|
||||
trigger.AgeDaysLowerThan = SystemHelper.TextToInteger(HttpContext.Request.Form[tFormKey][f], 0);
|
||||
|
||||
newTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> singleMarketSetting24hVolumeTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(smsFormKey + "Trigger_24hVolume") && k.EndsWith("|Min24hVolume"));
|
||||
foreach (string singleMarketSetting24hVolumeTriggerFormKey in singleMarketSetting24hVolumeTriggerFormKeys) {
|
||||
Trigger trigger = null;
|
||||
|
||||
string originalTriggerIndex = singleMarketSetting24hVolumeTriggerFormKey.Replace(smsFormKey + "Trigger_24hVolume", "").Replace("|Min24hVolume", "");
|
||||
string tFormKey = smsFormKey + "Trigger_24hVolume" + originalTriggerIndex + "|";
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey + "Min24hVolume"].Count; f++) {
|
||||
trigger = new Trigger();
|
||||
|
||||
trigger.Min24hVolume = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "Min24hVolume"][f], 0, "en-US");
|
||||
trigger.Max24hVolume = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "Max24hVolume"][f], Constants.Max24hVolume, "en-US");
|
||||
|
||||
newTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
sms.Triggers = newTriggers;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Off Triggers
|
||||
List<OffTrigger> newOffTriggers = new List<OffTrigger>();
|
||||
List<string> singleMarketSettingOffTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(smsFormKey + "OffTrigger_") && k.EndsWith("|MarketTrendName"));
|
||||
foreach (string singleMarketSettingOffTriggerFormKey in singleMarketSettingOffTriggerFormKeys) {
|
||||
OffTrigger offTrigger = null;
|
||||
|
||||
string originalOffTriggerNameSimplified = singleMarketSettingOffTriggerFormKey.Replace(smsFormKey + "OffTrigger_", "").Replace("|MarketTrendName", "");
|
||||
string tFormKey = smsFormKey + "OffTrigger_" + originalOffTriggerNameSimplified + "|";
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey + "MarketTrendName"].Count; f++) {
|
||||
if (originalOffTriggerNameSimplified.Equals("")) {
|
||||
offTrigger = new OffTrigger();
|
||||
} else {
|
||||
offTrigger = sms.OffTriggers.Find(t => SystemHelper.StripBadCode(t.MarketTrendName, Constants.WhiteListNames).Equals(originalOffTriggerNameSimplified));
|
||||
}
|
||||
|
||||
offTrigger.MarketTrendName = HttpContext.Request.Form[tFormKey + "MarketTrendName"][f];
|
||||
offTrigger.MarketTrendRelation = HttpContext.Request.Form[tFormKey + "MarketTrendRelation"][f];
|
||||
offTrigger.MinChange = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "MinChange"][f], Constants.MinTrendChange, "en-US");
|
||||
offTrigger.MaxChange = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "MaxChange"][f], Constants.MaxTrendChange, "en-US");
|
||||
|
||||
newOffTriggers.Add(offTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> singleMarketSettingHoursActiveOffTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(smsFormKey + "OffTrigger_HoursSinceTriggered"));
|
||||
foreach (string singleMarketSettingHoursActiveOffTriggerFormKey in singleMarketSettingHoursActiveOffTriggerFormKeys) {
|
||||
OffTrigger offTrigger = null;
|
||||
|
||||
string originalOffTriggerIndex = singleMarketSettingHoursActiveOffTriggerFormKey.Replace(smsFormKey + "OffTrigger_HoursSinceTriggered", "");
|
||||
string tFormKey = smsFormKey + "OffTrigger_HoursSinceTriggered" + originalOffTriggerIndex;
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey].Count; f++) {
|
||||
offTrigger = new OffTrigger();
|
||||
|
||||
offTrigger.HoursSinceTriggered = SystemHelper.TextToInteger(HttpContext.Request.Form[tFormKey][f], 0);
|
||||
|
||||
newOffTriggers.Add(offTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> singleMarketSetting24hVolumeOffTriggerFormKeys = formKeys.FindAll(k => k.StartsWith(smsFormKey + "OffTrigger_24hVolume") && k.EndsWith("|Min24hVolume"));
|
||||
foreach (string singleMarketSetting24hVolumeOffTriggerFormKey in singleMarketSetting24hVolumeOffTriggerFormKeys) {
|
||||
OffTrigger offTrigger = null;
|
||||
|
||||
string originalOffTriggerIndex = singleMarketSetting24hVolumeOffTriggerFormKey.Replace(smsFormKey + "OffTrigger_24hVolume", "").Replace("|Min24hVolume", "");
|
||||
string tFormKey = smsFormKey + "OffTrigger_24hVolume" + originalOffTriggerIndex + "|";
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[tFormKey + "Min24hVolume"].Count; f++) {
|
||||
offTrigger = new OffTrigger();
|
||||
|
||||
offTrigger.Min24hVolume = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "Min24hVolume"][f], 0, "en-US");
|
||||
offTrigger.Max24hVolume = SystemHelper.TextToDouble(HttpContext.Request.Form[tFormKey + "Max24hVolume"][f], Constants.Max24hVolume, "en-US");
|
||||
|
||||
newOffTriggers.Add(offTrigger);
|
||||
}
|
||||
}
|
||||
sms.OffTriggers = newOffTriggers;
|
||||
|
||||
#endregion
|
||||
|
||||
// Pairs Properties
|
||||
Dictionary<string, object> newPairsProperties = GetProfitTrailerProperties(formKeys, smsFormKey, "Pairs");
|
||||
sms.PairsProperties = newPairsProperties;
|
||||
|
||||
// DCA Properties
|
||||
Dictionary<string, object> newDCAProperties = GetProfitTrailerProperties(formKeys, smsFormKey, "DCA");
|
||||
sms.DCAProperties = newDCAProperties;
|
||||
|
||||
// Indicators Properties
|
||||
Dictionary<string, object> newIndicatorsProperties = GetProfitTrailerProperties(formKeys, smsFormKey, "Indicators");
|
||||
sms.IndicatorsProperties = newIndicatorsProperties;
|
||||
|
||||
newSingleMarketMarketSettings.Add(sms);
|
||||
}
|
||||
PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings = newSingleMarketMarketSettings;
|
||||
}
|
||||
|
||||
private Dictionary<string, object> GetProfitTrailerProperties(List<string> formKeys, string sFormKey, string propertyType) {
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
List<string> globalSettingPairsPropertiesFormKeys = formKeys.FindAll(k => k.StartsWith(sFormKey + propertyType + "Property_") && k.IndexOf("|Value") == -1);
|
||||
foreach (string globalSettingPairsFormKey in globalSettingPairsPropertiesFormKeys) {
|
||||
string originalKeySimplified = globalSettingPairsFormKey.Replace(sFormKey + propertyType + "Property_", "");
|
||||
string propertyFormKey = sFormKey + propertyType + "Property_" + originalKeySimplified;
|
||||
|
||||
for (int f = 0; f < HttpContext.Request.Form[propertyFormKey].Count; f++) {
|
||||
string propertyKey = HttpContext.Request.Form[propertyFormKey][f] + HttpContext.Request.Form[propertyFormKey + "|ValueMode"][f];
|
||||
string propertyValueString = HttpContext.Request.Form[propertyFormKey + "|Value"][f];
|
||||
|
||||
object propertyValue = new object();
|
||||
if (propertyValueString.Equals("true", StringComparison.InvariantCultureIgnoreCase) | propertyValueString.Equals("false", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
propertyValue = Convert.ToBoolean(propertyValueString);
|
||||
} else {
|
||||
if (SystemHelper.IsDouble(propertyValueString, "en-US")) {
|
||||
propertyValue = SystemHelper.TextToDouble(propertyValueString, 0, "en-US");
|
||||
|
||||
if (((double)propertyValue % 1) == 0) {
|
||||
propertyValue = Convert.ToInt32(propertyValue);
|
||||
}
|
||||
} else {
|
||||
propertyValue = propertyValueString;
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(propertyKey, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
@page
|
||||
@model SettingsGeneralModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/switchery/css/switchery.min.css" rel="stylesheet" />
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">General Settings</h4>
|
||||
|
||||
<p>
|
||||
In this area you may change the settings of your <b>settings.general.json</b> file. Please use this with caution as all settings you saved here will directly affect your PT Magic bot.
|
||||
</p>
|
||||
<p class="text-danger">Please note: When you save your settings using this interface, all existing comments will be removed from your settings.general.json!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Restore Backup<span></span></h4>
|
||||
|
||||
<p>
|
||||
PT Magic automatically creates a backup when you save your settings.general.json file using this interface. In case you notice some suspicious behaviour or just want to go back to your previous settings, you may restore the most recent backup here.
|
||||
</p>
|
||||
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light" data-toggle="modal" data-target="#modalRestoreBackup" type="submit">
|
||||
Restore Backup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal m-t-20" method="post">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Application</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Is Enabled <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Enables the PTMagic bot."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Application_IsEnabled" checked="@(Model.PTMagicConfiguration.GeneralSettings.Application.IsEnabled)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Test Mode <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If TestMode is active, no properties files will be changed"></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Application_TestMode" checked="@(Model.PTMagicConfiguration.GeneralSettings.Application.TestMode)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Profit Trailer Major Version <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Major version of your Profit Trailer."></i></label>
|
||||
<div class="col-md-8">
|
||||
@Model.PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMajorVersion
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Profit Trailer Path <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Path to your Profit Trailer main directory."></i></label>
|
||||
<div class="col-md-8">
|
||||
@Model.PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerPath
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Profit Trailer License <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Your Profit Trailer license key (needed to change your settings for PT 2.0 and above)"></i></label>
|
||||
<div class="col-md-8">
|
||||
@Model.PTMagicConfiguration.GetProfitTrailerLicenseKeyMasked()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Profit Trailer Monitor URL <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The URL to your profit trailer monitor (needed to change your settings for PT 2.0 and above)"></i></label>
|
||||
<div class="col-md-8">
|
||||
<a href="@Model.PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL" target="_blank">@Model.PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Profit Trailer Default Setting Name <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Your Profit Trailer default setting name (needed to change your settings for PT 2.0 and above)"></i></label>
|
||||
<div class="col-md-8">
|
||||
@Model.PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerDefaultSettingName
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Exchange <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The exchange your are running Profit Trailer on."></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="Application_Exchange" class="form-control">
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Application.Exchange.Equals("Binance", StringComparison.InvariantCultureIgnoreCase))">Binance</option>
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Application.Exchange.Equals("Bittrex", StringComparison.InvariantCultureIgnoreCase))">Bittrex</option>
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Application.Exchange.Equals("Poloniex", StringComparison.InvariantCultureIgnoreCase))">Poloniex</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Start Balance <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The balance you had in your wallet when you started working with Profit Trailer."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Application_StartBalance" value="@Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Timezone Offset <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Your timezone offset from GMT."></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="Application_TimezoneOffset" class="form-control">
|
||||
@Html.Raw(Model.GetTimezoneSelection())
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Always Load Default Before Switch <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If this is enabled, PTMagic will always load default settings before switching to another setting."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Application_AlwaysLoadDefaultBeforeSwitch" checked="@(Model.PTMagicConfiguration.GeneralSettings.Application.AlwaysLoadDefaultBeforeSwitch)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Flood Protection Minutes <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If a price trend is just zig-zagging around its trigger, you may want to protect your settings from getting switched back and forth every minute."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Application_FloodProtectionMinutes" value="@Model.PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes.ToString()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Instance Name <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The name of the instance of this bot. This will be used in your monitor and your Telegram messages. In case you are running more than one bot, you may set different names to separate them."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Application_InstanceName" value="@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Monitor</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Is Password Protected <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Defines if your monitor will be asking to setup a password on its first start."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Monitor_IsPasswordProtected" checked="@(Model.PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Open Browser On Start <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If active, a browser window will open as soon as you start the monitor."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Monitor_OpenBrowserOnStart" checked="@(Model.PTMagicConfiguration.GeneralSettings.Monitor.OpenBrowserOnStart)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Port <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The port you want to run your monitor on"></i></label>
|
||||
<div class="col-md-8">
|
||||
@Model.PTMagicConfiguration.GeneralSettings.Monitor.Port
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">RootUrl <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The root URL of your monitor website"></i></label>
|
||||
<div class="col-md-8">
|
||||
@Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Graph Interval Minutes <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The interval for the monitor market trend graph to draw points."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_GraphIntervalMinutes" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Graph Max Timeframe Hours <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="This defines the timeframe that your graph for market trends covers."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_GraphMaxTimeframeHours" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Refresh Seconds <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor main page."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_RefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Bag AnalyzerRefresh Seconds <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor bag analyzer page."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_BagAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Buy AnalyzerRefresh Seconds <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The refresh interval of your monitor buy analyzer page."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_BuyAnalyzerRefreshSeconds" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Link Platform <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The platform to which the pair name will link if you click on it."></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="Monitor_LinkPlatform" class="form-control">
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform.Equals("TradingView", StringComparison.InvariantCultureIgnoreCase))">TradingView</option>
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase))">Exchange</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Top Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of top markets being show in your Sales Analyzer."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_MaxTopMarkets" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Daily Summaries <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of Last Days being shown in your Sales Analyzer."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_MaxDailySummaries" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Monthly Summaries <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of Last Months being shown in your Sales Analyzer."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_MaxMonthlySummaries" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Dashboard Buy Entries <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of entries being shown in your dashboard for possible buys."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_MaxDashboardBuyEntries" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Dashboard Bag Entries <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of top markets being shown in your dashboard for pairs and dca."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_MaxDashboardBagEntries" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBagEntries.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max DCA Pairs <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The number of pairs (rows) to be shown in your DCA calculator."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Monitor_MaxDCAPairs" value="@Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDCAPairs.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Default DCA Mode <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Set the default mode of your DCA calculator.."></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="Monitor_DefaultDCAMode" class="form-control">
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Monitor.DefaultDCAMode.Equals("Simple", StringComparison.InvariantCultureIgnoreCase))">Simple</option>
|
||||
<option selected="@(Model.PTMagicConfiguration.GeneralSettings.Monitor.DefaultDCAMode.Equals("Advanced", StringComparison.InvariantCultureIgnoreCase))">Advanced</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Backup</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Is Enabled <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Enables a backup procedure for your properties files. Before every switch PTMagic will backup the current properties."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Backup_IsEnabled" checked="@(Model.PTMagicConfiguration.GeneralSettings.Backup.IsEnabled)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Hours <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Max number of hours to keep backup files."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Backup_MaxHours" value="@Model.PTMagicConfiguration.GeneralSettings.Backup.MaxHours.ToString(new System.Globalization.CultureInfo("en-US"))">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Telegram</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Is Enabled <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Enables PT Magic to send Telegram messages."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Telegram_IsEnabled" checked="@(Model.PTMagicConfiguration.GeneralSettings.Telegram.IsEnabled)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Bot Token <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Your Telegram bot token."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Telegram_BotToken" id="Telegram_BotToken" value="@Model.PTMagicConfiguration.GeneralSettings.Telegram.BotToken">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Chat Id <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Your Telegram Chat ID."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Telegram_ChatId" id="Telegram_ChatId" value="@Model.PTMagicConfiguration.GeneralSettings.Telegram.ChatId">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Silent Mode <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="If SilentMode is active, no notification sound or vibration will happen when the bot sends a Telegram message."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="Telegram_SilentMode" id="Telegram_SilentMode" checked="@(Model.PTMagicConfiguration.GeneralSettings.Telegram.SilentMode)" data-plugin="switchery" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label"></label>
|
||||
<div class="col-md-8">
|
||||
<button id="btn-test-telegram" class="btn btn-ptmagic btn-block text-uppercase waves-effect waves-light">
|
||||
Send Telegram Test Message
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Model.ValidationMessage.Equals("")) {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box text-danger">
|
||||
@Model.ValidationMessage
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<a class="btn btn-ptmagic btn-block text-uppercase waves-effect waves-light" data-toggle="modal" data-target="#modalSaveSettings" href="#">
|
||||
Save Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalRestoreBackup" tabindex="-1" role="dialog" aria-labelledby="modalRestoreBackupTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalRestoreBackupTitle">Are you sure?</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do you really want to restore a backup of your settings.general.json and overwrite all current settings?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-restorebackup" data-datatarget="settings.general.json">Yes, do it!</button>
|
||||
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">No...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalSaveSettings" tabindex="-1" role="dialog" aria-labelledby="modalSaveSettingsTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalSaveSettingsTitle">Are you sure?</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Do you really want to save the current settings?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-savesettings">Yes, do it!</button>
|
||||
<button type="button" class="btn btn-secondary text-uppercase" data-dismiss="modal">No...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/switchery/js/switchery.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
|
||||
$('#btn-test-telegram').click(function () {
|
||||
|
||||
var telegramSettings = { Telegram_BotToken: $('#Telegram_BotToken').val(), Telegram_ChatId: $('#Telegram_ChatId').val(), Telegram_SilentMode: $('#Telegram_SilentMode').val() };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/TestTelegram",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(telegramSettings),
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("XSRF-TOKEN",
|
||||
$('input:hidden[name="__RequestVerificationToken"]').val());
|
||||
},
|
||||
success: function (data) {
|
||||
$.Notification.notify('success', 'top left', 'Telegram message sent!', 'Telegram test message sent successfully.');
|
||||
},
|
||||
error: function (jqxhr, errorText, thrownError) {
|
||||
$.Notification.notify('error', 'top left', 'Error sending Telegram message!', 'Error message: ' + errorText);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('click', '.btn-savesettings', function () {
|
||||
$('form').submit();
|
||||
});
|
||||
|
||||
$('.btn-restorebackup').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
|
||||
var postValues = { File: dataTarget };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/RestoreBackup",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(postValues),
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("XSRF-TOKEN",
|
||||
$('input:hidden[name="__RequestVerificationToken"]').val());
|
||||
},
|
||||
success: function (data) {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsGeneral?n=BackupRestored");
|
||||
},
|
||||
error: function (jqxhr, errorText, thrownError) {
|
||||
$.Notification.notify('error', 'top left', 'Error restoring backup!', 'Error message: ' + errorText);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
@if (!Model.NotifyType.Equals("") && !Model.NotifyHeadline.Equals("") && !Model.NotifyMessage.Equals("")) {
|
||||
<text>
|
||||
$.Notification.notify('@Model.NotifyType', 'top left', '@Model.NotifyHeadline', '@Model.NotifyMessage');
|
||||
window.history.pushState('@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor', '@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor', '@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsGeneral');
|
||||
</text>
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SettingsGeneralModel : _Internal.BasePageModelSecure {
|
||||
public string ValidationMessage = "";
|
||||
|
||||
private string GetTimezoneOffsetString(TimeZoneInfo tzi) {
|
||||
string result = "";
|
||||
|
||||
result += (tzi.BaseUtcOffset >= TimeSpan.Zero) ? "+" : "-";
|
||||
result += Math.Abs(tzi.BaseUtcOffset.Hours).ToString().Trim();
|
||||
result += ":";
|
||||
result += Math.Abs(tzi.BaseUtcOffset.Minutes).ToString("00").Trim();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetTimezoneSelection() {
|
||||
string result = "";
|
||||
|
||||
List<string> tzOffsetList = new List<string>();
|
||||
foreach (TimeZoneInfo tzi in TimeZoneInfo.GetSystemTimeZones()) {
|
||||
string offsetString = this.GetTimezoneOffsetString(tzi);
|
||||
if (!tzOffsetList.Contains(offsetString)) {
|
||||
string selected = "";
|
||||
if (PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Equals(offsetString, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
|
||||
result += "<option" + selected + ">" + offsetString + "</option>\n";
|
||||
tzOffsetList.Add(offsetString);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
string notification = GetStringParameter("n", "");
|
||||
if (notification.Equals("BackupRestored")) {
|
||||
NotifyHeadline = "Backup restored!";
|
||||
NotifyMessage = "Your backup of settings.general.json was successfully restored.";
|
||||
NotifyType = "success";
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
base.Init();
|
||||
|
||||
PTMagicConfiguration.GeneralSettings.Application.IsEnabled = HttpContext.Request.Form["Application_IsEnabled"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Application.TestMode = HttpContext.Request.Form["Application_TestMode"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Application.Exchange = HttpContext.Request.Form["Application_Exchange"];
|
||||
PTMagicConfiguration.GeneralSettings.Application.StartBalance = SystemHelper.TextToDouble(HttpContext.Request.Form["Application_StartBalance"], PTMagicConfiguration.GeneralSettings.Application.StartBalance, "en-US");
|
||||
PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset = HttpContext.Request.Form["Application_TimezoneOffset"].ToString().Replace(" ", "");
|
||||
PTMagicConfiguration.GeneralSettings.Application.AlwaysLoadDefaultBeforeSwitch = HttpContext.Request.Form["Application_AlwaysLoadDefaultBeforeSwitch"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Application_FloodProtectionMinutes"], PTMagicConfiguration.GeneralSettings.Application.FloodProtectionMinutes);
|
||||
PTMagicConfiguration.GeneralSettings.Application.InstanceName = HttpContext.Request.Form["Application_InstanceName"];
|
||||
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected = HttpContext.Request.Form["Monitor_IsPasswordProtected"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.OpenBrowserOnStart = HttpContext.Request.Form["Monitor_OpenBrowserOnStart"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_GraphIntervalMinutes"], PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_GraphMaxTimeframeHours"], PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_RefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.RefreshSeconds);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BagAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BagAnalyzerRefreshSeconds);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_BuyAnalyzerRefreshSeconds"], PTMagicConfiguration.GeneralSettings.Monitor.BuyAnalyzerRefreshSeconds);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform = HttpContext.Request.Form["Monitor_LinkPlatform"];
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxTopMarkets"], PTMagicConfiguration.GeneralSettings.Monitor.MaxTopMarkets);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxDailySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxDailySummaries);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxMonthlySummaries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxMonthlySummaries);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxDashboardBuyEntries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBagEntries = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxDashboardBagEntries"], PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBagEntries);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.MaxDCAPairs = SystemHelper.TextToInteger(HttpContext.Request.Form["Monitor_MaxDCAPairs"], PTMagicConfiguration.GeneralSettings.Monitor.MaxDCAPairs);
|
||||
PTMagicConfiguration.GeneralSettings.Monitor.DefaultDCAMode = HttpContext.Request.Form["Monitor_DefaultDCAMode"];
|
||||
|
||||
PTMagicConfiguration.GeneralSettings.Backup.IsEnabled = HttpContext.Request.Form["Backup_IsEnabled"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Backup.MaxHours = SystemHelper.TextToInteger(HttpContext.Request.Form["Backup_MaxHours"], PTMagicConfiguration.GeneralSettings.Backup.MaxHours);
|
||||
|
||||
PTMagicConfiguration.GeneralSettings.Telegram.IsEnabled = HttpContext.Request.Form["Telegram_IsEnabled"].Equals("on");
|
||||
PTMagicConfiguration.GeneralSettings.Telegram.BotToken = HttpContext.Request.Form["Telegram_BotToken"].ToString().Trim();
|
||||
PTMagicConfiguration.GeneralSettings.Telegram.ChatId = SystemHelper.TextToInteger64(HttpContext.Request.Form["Telegram_ChatId"], PTMagicConfiguration.GeneralSettings.Telegram.ChatId);
|
||||
PTMagicConfiguration.GeneralSettings.Telegram.SilentMode = HttpContext.Request.Form["Telegram_SilentMode"].Equals("on");
|
||||
|
||||
PTMagicConfiguration.WriteGeneralSettings(PTMagicBasePath);
|
||||
|
||||
NotifyHeadline = "Settings saved!";
|
||||
NotifyMessage = "Settings saved successfully to settings.general.json.";
|
||||
NotifyType = "success";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
@page
|
||||
@model SetupPasswordModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
Layout = "_LayoutPublic";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
|
||||
}
|
||||
|
||||
<div class="card-box">
|
||||
<div class="panel-heading">
|
||||
<h3 class="text-center"> <strong class="text-custom">PT Magic</strong> </h3>
|
||||
<p class="text-center">Setup your password.</p>
|
||||
</div>
|
||||
|
||||
<div class="p-20">
|
||||
<form class="form-horizontal m-t-20" method="post">
|
||||
<div class="form-group">
|
||||
<div class="col-12">
|
||||
<input name="Password" class="form-control" type="password" required="" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-12">
|
||||
<input name="PasswordConfirm" class="form-control" type="password" required="" compare="Password" placeholder="Confirm Password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Model.ValidationMessage.Equals("")) {
|
||||
<div class="form-group">
|
||||
<div class="text-danger m-l-10">
|
||||
@Model.ValidationMessage
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group text-center m-t-40">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-ptmagic btn-block text-uppercase waves-effect waves-light" type="submit">
|
||||
Save password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Core.Main;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SetupPasswordModel : _Internal.BasePageModel {
|
||||
public string ValidationMessage = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.PreInit();
|
||||
}
|
||||
|
||||
public void OnPost(string password, string passwordConfirm) {
|
||||
if (!password.Equals(passwordConfirm)) {
|
||||
ValidationMessage = "Password does not match the confirmation!";
|
||||
}
|
||||
|
||||
if (ModelState.IsValid) {
|
||||
base.PreInit();
|
||||
PTMagicConfiguration.WriteSecureSettings(password, PTMagicBasePath);
|
||||
|
||||
Response.Redirect(PTMagicConfiguration.GeneralSettings.Monitor.RootUrl + "Login");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
@page
|
||||
@model StatusSummaryModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">PTMagic Status <small id="last-refresh" class="pull-right"></small></h4>
|
||||
@{
|
||||
DateTime lastRuntime = Model.Summary.LastRuntime;
|
||||
double elapsedSecondsSinceRuntime = DateTime.Now.Subtract(lastRuntime).TotalSeconds;
|
||||
double intervalSeconds = Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes * 60.0;
|
||||
|
||||
string ptMagicHealthIcon = "<i class=\"fa fa-heartbeat text-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"PT Magic is alive and healthy!\"></i>";
|
||||
if (elapsedSecondsSinceRuntime > (intervalSeconds + intervalSeconds * 0.2)) {
|
||||
ptMagicHealthIcon = "<i class=\"fa fa-bolt text-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"PT Magic seems to have problems, check the logs!\"></i>";
|
||||
}
|
||||
|
||||
string floodProtectionIcon = "";
|
||||
if (Model.Summary.FloodProtectedSetting != null) {
|
||||
floodProtectionIcon = "<i class=\"fa fa-info-circle text-warning\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Flood protection active! Not switching setting to " + Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.FloodProtectedSetting.SettingName) + " .\"></i>";
|
||||
}
|
||||
|
||||
string lastGlobalSettingSwitch = Core.Helper.SystemHelper.GetProperDurationTime((int)Math.Ceiling(DateTime.Now.Subtract(Model.Summary.LastGlobalSettingSwitch).TotalSeconds)) + " ago";
|
||||
if (Model.Summary.LastGlobalSettingSwitch == Core.Main.Constants.confMinDate) {
|
||||
lastGlobalSettingSwitch = "-";
|
||||
}
|
||||
|
||||
int activeSingleSettings = Model.MarketsWithSingleSettings.Count;
|
||||
string singleSettingInfoIcon = "";
|
||||
if (activeSingleSettings > 0) {
|
||||
singleSettingInfoIcon = "<i class=\"fa fa-info-circle text-muted\" data-toggle=\"tooltip\" data-placement=\"top\" data-html=\"true\" title=\"<b>Single Market Settings active for:</b><br />-" + Core.Helper.SystemHelper.ConvertListToTokenString(Model.MarketsWithSingleSettings, "<br />-", true) + "\" data-template=\"<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner tooltip-200 text-left'></div></div>\"></i>";
|
||||
}
|
||||
}
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Active Global Setting</td>
|
||||
<td class="text-right">@Html.Raw(floodProtectionIcon) @Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.CurrentGlobalSetting.SettingName)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Runtime</td>
|
||||
<td class="text-right"><span id="ptmagic-health">@Html.Raw(ptMagicHealthIcon)</span> @Core.Helper.SystemHelper.GetProperDurationTime((int)Math.Ceiling(elapsedSecondsSinceRuntime)) ago</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Runtime Length</td>
|
||||
<td class="text-right"> @Core.Helper.SystemHelper.GetProperDurationTime(Model.Summary.LastRuntimeSeconds)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Setting Switch</td>
|
||||
<td class="text-right">@lastGlobalSettingSwitch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Active Single Settings</td>
|
||||
<td class="text-right"><a href="ManageSMS">@Html.Raw(singleSettingInfoIcon)</a> @activeSingleSettings</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Active Settings</h4>
|
||||
@{
|
||||
string maxCostCaption = "Max";
|
||||
if (Model.Summary.ProfitTrailerMajorVersion > 1) {
|
||||
maxCostCaption = "Initial";
|
||||
}
|
||||
}
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Max Pairs</td>
|
||||
<td class="text-right">@Model.Summary.MaxTradingPairs.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
@if (Model.Summary.MaxCost > 0) {
|
||||
<td>@maxCostCaption Cost</td>
|
||||
<td class="text-right">@Model.Summary.MaxCost.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
} else {
|
||||
<td>@maxCostCaption Cost %</td>
|
||||
<td class="text-right">@Model.Summary.MaxCostPercentage.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DCA Level</td>
|
||||
<td class="text-right">@Model.Summary.DCALevels.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td>Min Vol.</td>
|
||||
<td class="text-right">@Model.Summary.MinBuyVolume.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trailing Buy</td>
|
||||
<td class="text-right">@Model.Summary.TrailingBuy.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td>Trailing Profit</td>
|
||||
<td class="text-right">@Model.Summary.TrailingProfit.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Active Buy Strategies</h4>
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<tbody>
|
||||
@if (Model.Summary.BuyStrategies.Count == 0) {
|
||||
<tr>
|
||||
<td>Buy Strat.</td>
|
||||
<td class="text-right">@Model.Summary.BuyStrategy</td>
|
||||
<td>Buy Value</td>
|
||||
<td class="text-right">@Model.Summary.BuyValue.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
} else {
|
||||
char buyStrategyIndex = 'A';
|
||||
foreach (Core.Main.DataObjects.PTMagicData.StrategySummary buyStrategy in Model.Summary.BuyStrategies) {
|
||||
<tr>
|
||||
<td>Buy Strat. @buyStrategyIndex</td>
|
||||
<td class="text-right">@buyStrategy.Name</td>
|
||||
<td>Buy Value @buyStrategyIndex</td>
|
||||
<td class="text-right">@buyStrategy.Value.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
buyStrategyIndex++;
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Active Sell Strategies</h4>
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<tbody>
|
||||
@if (Model.Summary.SellStrategies.Count == 0) {
|
||||
<tr>
|
||||
<td>Sell Strat.</td>
|
||||
<td class="text-right">@Model.Summary.SellStrategy</td>
|
||||
<td>Sell Value</td>
|
||||
<td class="text-right">@Model.Summary.SellValue.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
} else {
|
||||
char sellStrategyIndex = 'A';
|
||||
foreach (Core.Main.DataObjects.PTMagicData.StrategySummary sellStrategy in Model.Summary.SellStrategies) {
|
||||
<tr>
|
||||
<td>Sell Strat. @sellStrategyIndex</td>
|
||||
<td class="text-right">@sellStrategy.Name</td>
|
||||
<td>Sell Value @sellStrategyIndex</td>
|
||||
<td class="text-right">@sellStrategy.Value.ToString(new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
sellStrategyIndex++;
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Settings Active Time (Last 24h)</h4>
|
||||
|
||||
<div id="gsChart24h">
|
||||
<svg style="height:300px;width:100%"></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Settings Active Time (Last 3 days)</h4>
|
||||
|
||||
<div id="gsChart3d">
|
||||
<svg style="height:300px;width:100%"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Global Settings Log</h4>
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Time the setting got activated.">Activation Time</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Name of the global setting.">Setting</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Amount of time the setting is or was active">Active Time</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Market trends values from the time the setting got activated.">Market Trends</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.GlobalSettingSummary gss in Model.Summary.GlobalSettingSummary.OrderByDescending(g => g.SwitchDateTime).Take(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxSettingsLogEntries)) {
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
DateTimeOffset settingActivationTime = gss.SwitchDateTime;
|
||||
settingActivationTime = settingActivationTime.ToOffset(offsetTimeSpan);
|
||||
|
||||
string marketTrendsSummary = "";
|
||||
foreach (string mt in gss.MarketTrendChanges.Keys) {
|
||||
if (!marketTrendsSummary.Equals("")) {
|
||||
marketTrendsSummary += "<br />";
|
||||
}
|
||||
marketTrendsSummary += Core.Helper.SystemHelper.SplitCamelCase(mt) + ": " + gss.MarketTrendChanges[mt].TrendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
}
|
||||
<tr>
|
||||
<td>@settingActivationTime.DateTime.ToShortDateString() @settingActivationTime.DateTime.ToShortTimeString()</td>
|
||||
<td>@Core.Helper.SystemHelper.SplitCamelCase(gss.SettingName)</td>
|
||||
<td>@Core.Helper.SystemHelper.GetProperDurationTime(gss.ActiveSeconds)</td>
|
||||
<td>@Html.Raw(marketTrendsSummary)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/d3/d3.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/nvd3/nv.d3.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
|
||||
@if (!Model.SettingsDistribution24hChartDataJSON.Equals("")) {
|
||||
<text>
|
||||
nv.addGraph(function() {
|
||||
var chart = nv.models.pieChart()
|
||||
.x(function(d) { return d.label })
|
||||
.y(function(d) { return d.value })
|
||||
.showLabels(true) //Display pie labels
|
||||
.labelThreshold(.1) //Configure the minimum slice size for labels to show up
|
||||
.labelType("percent") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
|
||||
.donut(true) //Turn on Donut mode. Makes pie chart look tasty!
|
||||
.donutRatio(0.3) //Configure how big you want the donut hole size to be.
|
||||
;
|
||||
|
||||
d3.select("#gsChart24h svg")
|
||||
.datum(@Html.Raw(Model.SettingsDistribution24hChartDataJSON))
|
||||
.transition().duration(350)
|
||||
.call(chart);
|
||||
|
||||
return chart;
|
||||
});
|
||||
</text>
|
||||
}
|
||||
|
||||
@if (!Model.SettingsDistribution3dChartDataJSON.Equals("")) {
|
||||
<text>
|
||||
nv.addGraph(function() {
|
||||
var chart = nv.models.pieChart()
|
||||
.x(function(d) { return d.label })
|
||||
.y(function(d) { return d.value })
|
||||
.showLabels(true) //Display pie labels
|
||||
.labelThreshold(.1) //Configure the minimum slice size for labels to show up
|
||||
.labelType("percent") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
|
||||
.donut(true) //Turn on Donut mode. Makes pie chart look tasty!
|
||||
.donutRatio(0.3) //Configure how big you want the donut hole size to be.
|
||||
;
|
||||
|
||||
d3.select("#gsChart3d svg")
|
||||
.datum(@Html.Raw(Model.SettingsDistribution3dChartDataJSON))
|
||||
.transition().duration(350)
|
||||
.call(chart);
|
||||
|
||||
return chart;
|
||||
});
|
||||
</text>
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class StatusSummaryModel : _Internal.BasePageModelSecure {
|
||||
public List<string> MarketsWithSingleSettings = new List<string>();
|
||||
public string SettingsDistribution24hChartDataJSON = "";
|
||||
public string SettingsDistribution3dChartDataJSON = "";
|
||||
private Dictionary<string, string> settingsChartColors = new Dictionary<string, string>();
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
BuildMarketsWithSingleSettings();
|
||||
BuildChartColors();
|
||||
Build24hChartData();
|
||||
Build3dChartData();
|
||||
}
|
||||
|
||||
private void BuildMarketsWithSingleSettings() {
|
||||
// Get markets with active single settings
|
||||
foreach (string key in Summary.MarketSummary.Keys) {
|
||||
if (Summary.MarketSummary[key].ActiveSingleSettings != null) {
|
||||
if (Summary.MarketSummary[key].ActiveSingleSettings.Count > 0) {
|
||||
MarketsWithSingleSettings.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
MarketsWithSingleSettings.Sort();
|
||||
}
|
||||
|
||||
private void BuildChartColors() {
|
||||
int settingIndex = 0;
|
||||
foreach (GlobalSetting globalSetting in PTMagicConfiguration.AnalyzerSettings.GlobalSettings) {
|
||||
string chartColor = "";
|
||||
if (settingIndex < Constants.ChartLineColors.Length) {
|
||||
chartColor = Constants.ChartLineColors[settingIndex];
|
||||
} else {
|
||||
chartColor = Constants.ChartLineColors[settingIndex - 20];
|
||||
}
|
||||
|
||||
settingsChartColors.Add(globalSetting.SettingName, chartColor);
|
||||
|
||||
settingIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private void Build24hChartData() {
|
||||
if (Summary.GlobalSettingSummary.Count > 0) {
|
||||
DateTime dateTime24hAgo = DateTime.Now.AddHours(-24);
|
||||
List<GlobalSettingSummary> gsSummaries24h = Summary.GlobalSettingSummary.FindAll(gss => gss.SwitchDateTime >= dateTime24hAgo);
|
||||
IEnumerable<GlobalSettingSummary> gsNames24h = gsSummaries24h.GroupBy(gss => gss.SettingName).Select(group => group.First());
|
||||
|
||||
if (Summary.GlobalSettingSummary.FindAll(gss => gss.SwitchDateTime <= dateTime24hAgo).Count > 0) {
|
||||
GlobalSettingSummary gsBefore24h = Summary.GlobalSettingSummary.FindAll(gss => gss.SwitchDateTime <= dateTime24hAgo).OrderByDescending(gss => gss.SwitchDateTime).First();
|
||||
if (gsBefore24h != null) {
|
||||
DateTime gsSwitchedOffDateTime = gsBefore24h.SwitchDateTime.AddSeconds(gsBefore24h.ActiveSeconds);
|
||||
if (gsSwitchedOffDateTime > dateTime24hAgo) {
|
||||
gsBefore24h.ActiveSeconds = (int)Math.Floor(gsSwitchedOffDateTime.Subtract(dateTime24hAgo).TotalSeconds);
|
||||
|
||||
gsSummaries24h.Add(gsBefore24h);
|
||||
|
||||
if (gsNames24h.Select(gss => gss.SettingName.Equals(gsBefore24h.SettingName)) == null) {
|
||||
gsNames24h.Append(gsBefore24h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gsNames24h.Count() > 0) {
|
||||
SettingsDistribution24hChartDataJSON = "[";
|
||||
int gssIndex = 0;
|
||||
double totalCoveredSeconds = gsSummaries24h.Sum(gs => gs.ActiveSeconds);
|
||||
foreach (GlobalSettingSummary gss in gsNames24h) {
|
||||
string lineColor = "";
|
||||
if (settingsChartColors.ContainsKey(gss.SettingName)) {
|
||||
lineColor = settingsChartColors[gss.SettingName];
|
||||
} else {
|
||||
if (gssIndex < Constants.ChartLineColors.Length) {
|
||||
lineColor = Constants.ChartLineColors[gssIndex];
|
||||
} else {
|
||||
lineColor = Constants.ChartLineColors[gssIndex - 20];
|
||||
}
|
||||
}
|
||||
|
||||
if (!SettingsDistribution24hChartDataJSON.Equals("[")) SettingsDistribution24hChartDataJSON += ",";
|
||||
|
||||
double gsActiveSeconds = gsSummaries24h.FindAll(gs => gs.SettingName.Equals(gss.SettingName)).Sum(gs => gs.ActiveSeconds);
|
||||
double chartValue = gsActiveSeconds / totalCoveredSeconds * 100;
|
||||
|
||||
SettingsDistribution24hChartDataJSON += "{";
|
||||
SettingsDistribution24hChartDataJSON += "label: '" + SystemHelper.SplitCamelCase(gss.SettingName) + "',";
|
||||
SettingsDistribution24hChartDataJSON += "color: '" + lineColor + "',";
|
||||
SettingsDistribution24hChartDataJSON += "value: " + chartValue.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "";
|
||||
SettingsDistribution24hChartDataJSON += "}";
|
||||
|
||||
gssIndex++;
|
||||
}
|
||||
SettingsDistribution24hChartDataJSON += "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Build3dChartData() {
|
||||
if (Summary.GlobalSettingSummary.Count > 0) {
|
||||
DateTime dateTime3dAgo = DateTime.Now.AddHours(-72);
|
||||
List<GlobalSettingSummary> gsSummaries3d = Summary.GlobalSettingSummary.FindAll(gss => gss.SwitchDateTime >= dateTime3dAgo);
|
||||
IEnumerable<GlobalSettingSummary> gsNames3d = gsSummaries3d.GroupBy(gss => gss.SettingName).Select(group => group.First());
|
||||
|
||||
if (Summary.GlobalSettingSummary.FindAll(gss => gss.SwitchDateTime <= dateTime3dAgo).Count > 0) {
|
||||
GlobalSettingSummary gsBefore3d = Summary.GlobalSettingSummary.FindAll(gss => gss.SwitchDateTime <= dateTime3dAgo).OrderByDescending(gss => gss.SwitchDateTime).First();
|
||||
if (gsBefore3d != null) {
|
||||
DateTime gsSwitchedOffDateTime = gsBefore3d.SwitchDateTime.AddSeconds(gsBefore3d.ActiveSeconds);
|
||||
if (gsSwitchedOffDateTime > dateTime3dAgo) {
|
||||
gsBefore3d.ActiveSeconds = (int)Math.Floor(gsSwitchedOffDateTime.Subtract(dateTime3dAgo).TotalSeconds);
|
||||
|
||||
gsSummaries3d.Add(gsBefore3d);
|
||||
|
||||
if (gsNames3d.Select(gss => gss.SettingName.Equals(gsBefore3d.SettingName)) == null) {
|
||||
gsNames3d.Append(gsBefore3d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gsNames3d.Count() > 0) {
|
||||
SettingsDistribution3dChartDataJSON = "[";
|
||||
int gssIndex = 0;
|
||||
double totalCoveredSeconds = gsSummaries3d.Sum(gs => gs.ActiveSeconds);
|
||||
foreach (GlobalSettingSummary gss in gsNames3d) {
|
||||
string lineColor = "";
|
||||
if (settingsChartColors.ContainsKey(gss.SettingName)) {
|
||||
lineColor = settingsChartColors[gss.SettingName];
|
||||
} else {
|
||||
if (gssIndex < Constants.ChartLineColors.Length) {
|
||||
lineColor = Constants.ChartLineColors[gssIndex];
|
||||
} else {
|
||||
lineColor = Constants.ChartLineColors[gssIndex - 20];
|
||||
}
|
||||
}
|
||||
|
||||
if (!SettingsDistribution3dChartDataJSON.Equals("[")) SettingsDistribution3dChartDataJSON += ",";
|
||||
|
||||
double gsActiveSeconds = gsSummaries3d.FindAll(gs => gs.SettingName.Equals(gss.SettingName)).Sum(gs => gs.ActiveSeconds);
|
||||
double chartValue = gsActiveSeconds / totalCoveredSeconds * 100;
|
||||
|
||||
SettingsDistribution3dChartDataJSON += "{";
|
||||
SettingsDistribution3dChartDataJSON += "label: '" + SystemHelper.SplitCamelCase(gss.SettingName) + "',";
|
||||
SettingsDistribution3dChartDataJSON += "color: '" + lineColor + "',";
|
||||
SettingsDistribution3dChartDataJSON += "value: " + chartValue.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "";
|
||||
SettingsDistribution3dChartDataJSON += "}";
|
||||
|
||||
gssIndex++;
|
||||
}
|
||||
SettingsDistribution3dChartDataJSON += "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
@page
|
||||
@model TransactionsModel
|
||||
@{
|
||||
ViewData["Title"] = "";
|
||||
}
|
||||
|
||||
@section Styles {
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/css/tablesaw.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/bootstrap-datepicker/css/bootstrap-datepicker.min.css" rel="stylesheet">
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/clockpicker/css/bootstrap-clockpicker.min.css" rel="stylesheet">
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Transactions</h4>
|
||||
|
||||
<p>
|
||||
In this area you may add manual transactions (like deposits and withdrawals) to your PT Magic data. Adding this kind of information will help PT Magic calculating your percentage gains and your balance more accurately.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal m-t-20" method="post">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">New Transaction</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Amount <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The amount of your transaction. Positive numbers for deposits and negative numbers for withdrawals."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="Transaction_Amount">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Date <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The date of your transaction."></i></label>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="mm/dd/yyyy" id="datepicker-autoclose" name="Transaction_Date">
|
||||
<span class="input-group-addon bg-custom b-0"><i class="md md-event-note text-dark"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Time <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The time of your transaction."></i></label>
|
||||
<div class="col-md-8">
|
||||
<div class="input-group clockpicker m-b-20" data-placement="top" data-align="top" data-autoclose="true">
|
||||
<input type="text" class="form-control" name="Transaction_Time">
|
||||
<span class="input-group-addon"> <span class="md md-access-time"></span> </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label"></label>
|
||||
<div class="col-md-8">
|
||||
<button class="btn btn-ptmagic btn-block text-uppercase waves-effect waves-light" type="submit">
|
||||
Save Transaction
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Model.ValidationMessage.Equals("")) {
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box text-danger">
|
||||
@Model.ValidationMessage
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 header-title">Your Transactions</h4>
|
||||
|
||||
@if (Model.TransactionData.Transactions.Count > 0) {
|
||||
<table class="tablesaw table m-b-0" data-tablesaw-sortable data-tablesaw-sortable-switch>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th scope="col" data-tablesaw-sortable-col data-tablesaw-sortable-default-col>Time</th>
|
||||
<th scope="col" data-tablesaw-sortable-col class="text-right">Amount</th>
|
||||
<th scope="col" data-tablesaw-sortable-col>Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.Transaction transaction in Model.TransactionData.Transactions) {
|
||||
<tr>
|
||||
<td style="width:20px;"><a href="#" class="btn-remove" data-transactionguid="@transaction.GUID"><i class="fa fa-remove text-danger"></i></a></td>
|
||||
<td>@transaction.GetLocalDateTime(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset).ToShortDateString() @transaction.GetLocalDateTime(Model.PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset).ToShortTimeString()</td>
|
||||
<td class="text-right text-autocolor">@transaction.Amount.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
@if (transaction.Amount > 0) {
|
||||
<td class="text-success">Deposit</td>
|
||||
} else {
|
||||
<td class="text-danger">Withdrawal</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} else {
|
||||
<p>No transactions found.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@section Scripts {
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/tablesaw/js/tablesaw-init.js"></script>
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/clockpicker/js/bootstrap-clockpicker.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
|
||||
jQuery('#datepicker-autoclose').datepicker({
|
||||
weekStart: 1,
|
||||
autoclose: true,
|
||||
todayHighlight: true
|
||||
});
|
||||
|
||||
$('.clockpicker').clockpicker({
|
||||
donetext: 'Done'
|
||||
});
|
||||
|
||||
@if (!Model.NotifyType.Equals("") && !Model.NotifyHeadline.Equals("") && !Model.NotifyMessage.Equals("")) {
|
||||
<text>
|
||||
$.Notification.notify('@Model.NotifyType', 'top left', '@Model.NotifyHeadline', '@Model.NotifyMessage');
|
||||
</text>
|
||||
}
|
||||
|
||||
$('.btn-remove').click(function () {
|
||||
var tGuid = $(this).data('transactionguid');
|
||||
var postData = { Transaction_GUID: tGuid };
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: "@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_post/RemoveTransaction",
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
data: JSON.stringify(postData),
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("XSRF-TOKEN",
|
||||
$('input:hidden[name="__RequestVerificationToken"]').val());
|
||||
},
|
||||
success: function (data) {
|
||||
$.Notification.notify('success', 'top left', 'Transaction removed!', 'Transaction "' + tGuid + '" was successfully removed.');
|
||||
window.location = window.location.href;
|
||||
},
|
||||
error: function (jqxhr, errorText, thrownError) {
|
||||
$.Notification.notify('error', 'top left', 'Error removing transaction "' + tGuid + '"!', 'Error message: ' + errorText);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class TransactionsModel : _Internal.BasePageModelSecure {
|
||||
public TransactionData TransactionData = null;
|
||||
public string ValidationMessage = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
TransactionData = new TransactionData(PTMagicBasePath);
|
||||
}
|
||||
|
||||
public void OnPost() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
|
||||
SaveTransaction();
|
||||
}
|
||||
|
||||
private void SaveTransaction() {
|
||||
double transactionAmount = 0;
|
||||
DateTimeOffset transactionDateTime = Constants.confMinDate;
|
||||
|
||||
try {
|
||||
transactionAmount = SystemHelper.TextToDouble(HttpContext.Request.Form["Transaction_Amount"], transactionAmount, "en-US");
|
||||
//transactionDateTime = DateTimeOffset.Parse(HttpContext.Request.Form["Transaction_Date"] + " " + HttpContext.Request.Form["Transaction_Time"], CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
|
||||
DateTime tmp = DateTime.Parse(HttpContext.Request.Form["Transaction_Date"] + " " + HttpContext.Request.Form["Transaction_Time"], CultureInfo.InvariantCulture, DateTimeStyles.None);
|
||||
|
||||
// Convert local offset time to UTC
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
transactionDateTime = new DateTimeOffset(tmp, offsetTimeSpan);
|
||||
} catch { }
|
||||
|
||||
if (transactionAmount == 0) {
|
||||
ValidationMessage = "Please enter a valid amount in the format 123.45!";
|
||||
} else {
|
||||
if (transactionDateTime == Constants.confMinDate) {
|
||||
ValidationMessage = "Please select a valid date and time!";
|
||||
} else {
|
||||
TransactionData.Transactions.Add(new Transaction() { GUID = Guid.NewGuid().ToString(), Amount = transactionAmount, UTCDateTime = transactionDateTime.UtcDateTime });
|
||||
TransactionData.SaveTransactions(PTMagicBasePath);
|
||||
|
||||
NotifyHeadline = "Transaction saved!";
|
||||
NotifyMessage = "Transaction saved successfully to _data/Transactions.json.";
|
||||
NotifyType = "success";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="The most complete all-in-one addon for Profit Trailer cryptocurrency trading bot." />
|
||||
<meta name="robots" content="NOINDEX, NOFOLLOW" />
|
||||
<link rel="shortcut icon" href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)favicon.ico">
|
||||
|
||||
<title>@Model.PTMagicConfiguration.GeneralSettings.Application.InstanceName Monitor @ViewData["Title"]</title>
|
||||
|
||||
@RenderSection("Styles", required: false)
|
||||
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/icons.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/style.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/custom.css?v=@Html.Raw(Model.CurrentBotVersion)" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/modernizr.min.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<!-- Navigation Bar-->
|
||||
<header id="topnav">
|
||||
<div class="topbar-main">
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Logo container-->
|
||||
<div class="logo">
|
||||
<a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)" class="logo"><i class="fa fa-magic text-ptmagic"></i> <span class="text-ptmagic">PTMagic</span></a>
|
||||
<span class="logo-version">
|
||||
v<span>@Model.CurrentBotVersion</span>
|
||||
@if (!Core.Helper.SystemHelper.IsRecentVersion(Model.CurrentBotVersion, Model.LatestVersion)) {
|
||||
<a href="https://github.com/Legedric/ptmagic/releases" target="_blank"><i class="fa fa-exclamation-triangle text-warning" data-toggle="tooltip" data-placement="top" title="Your PT Magic is out of date. Click here to get to the latest release."></i></a>
|
||||
} else {
|
||||
<i class="fa fa-check text-success" data-toggle="tooltip" data-placement="top" title="Your PT Magic is up to date."></i>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<!-- End Logo container-->
|
||||
|
||||
|
||||
<div class="menu-extras topbar-custom">
|
||||
<ul class="list-inline float-right mb-0">
|
||||
|
||||
<li class="menu-item list-inline-item">
|
||||
<!-- Mobile menu toggle-->
|
||||
<a class="navbar-toggle nav-link">
|
||||
<div class="lines">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</a>
|
||||
<!-- End mobile menu toggle-->
|
||||
</li>
|
||||
<li id="ticker-widgets" class="list-inline-item ticker-widgets"></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<!-- end menu-extras -->
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
</div> <!-- end container -->
|
||||
</div>
|
||||
<!-- end topbar-main -->
|
||||
|
||||
<div class="navbar-custom">
|
||||
<div class="container-fluid">
|
||||
<div id="navigation">
|
||||
<!-- Navigation Menu-->
|
||||
<ul class="navigation-menu">
|
||||
|
||||
<li>
|
||||
<a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)"><i class="fa fa-dashboard fa-2x"></i> <span> Dashboard </span></a>
|
||||
</li>
|
||||
|
||||
<li class="has-submenu">
|
||||
<a href="#"><i class="fa fa-line-chart fa-2x"></i> <span> Analyzers </span> <span class="menu-arrow"></span></a>
|
||||
<ul class="submenu">
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BuyAnalyzer"><i class="fa fa-shopping-cart"></i> Buy Analyzer</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BagAnalyzer"><i class="fa fa-shopping-bag"></i> Bag Analyzer</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SalesAnalyzer"><i class="fa fa-money"></i> Sales Analyzer</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer"><i class="fa fa-line-chart"></i> Market Analyzer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="has-submenu">
|
||||
<a href="#"><i class="fa fa-sliders fa-2x"></i> <span> Control </span> <span class="menu-arrow"></span></a>
|
||||
<ul class="submenu">
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)DCACalculator"><i class="fa fa-calculator"></i> DCA Calculator</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)StatusSummary"><i class="fa fa-clipboard"></i> Status & Summary</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)ManageSMS"><i class="fa fa-wrench"></i> Manage Single Market Settings</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@if (Model.PTMagicConfiguration.GeneralSettings.Monitor.IsPasswordProtected) {
|
||||
<li class="has-submenu">
|
||||
<a href="#"><i class="fa fa-cog fa-2x"></i> <span> Settings </span> <span class="menu-arrow"></span></a>
|
||||
<ul class="submenu">
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsGeneral"><i class="fa fa-power-off"></i> General</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SettingsAnalyzer"><i class="fa fa-magic"></i> Analyzer</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Transactions"><i class="fa fa-exchange"></i> Transactions</a></li>
|
||||
<li><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)PresetFiles"><i class="fa fa-edit"></i> Presets Files</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
} else {
|
||||
<li>
|
||||
<a data-toggle="tooltip" data-placement="top" title="Settings menu is only accessible when you protect your monitor with a password!"><i class="fa fa-lock text-danger"></i> <span> Settings</span></a>
|
||||
</li>
|
||||
}
|
||||
|
||||
<li>
|
||||
<a href="https://github.com/Legedric/ptmagic/wiki" target="_blank"><i class="fa fa-book fa-2x"></i> <span> Wiki </span></a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Application.ProfitTrailerMonitorURL)" target="_blank"><i class="fa fa-desktop fa-2x"></i> <span> PT Monitor <small class="text-muted">(@Html.Raw(Model.Summary.MainMarket + "@" + Model.PTMagicConfiguration.GeneralSettings.Application.Exchange))</small></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- End navigation menu -->
|
||||
</div> <!-- end #navigation -->
|
||||
</div> <!-- end container -->
|
||||
</div> <!-- end navbar-custom -->
|
||||
</header>
|
||||
<!-- End Navigation Bar-->
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="container-fluid">
|
||||
|
||||
@RenderBody()
|
||||
|
||||
</div> <!-- container -->
|
||||
|
||||
</div> <!-- content -->
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<a href="http://www.profit-trailer-magic.com/" target="_blank">www.profit-trailer-magic.com</a>
|
||||
|
|
||||
<a href="https://github.com/Legedric/ptmagic" target="_blank">GitHub</a>
|
||||
|
|
||||
<a href="https://github.com/Legedric/ptmagic/wiki" target="_blank">Wiki</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- End Footer -->
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
var resizefunc = [];
|
||||
</script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/popper.min.js"></script><!-- Popper for Bootstrap -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/bootstrap.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.slimscroll.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.scrollTo.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/custom.js?v=@Html.Raw(Model.CurrentBotVersion)"></script>
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/notifyjs/js/notify.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/plugins/notifications/notify-metro.js"></script>
|
||||
|
||||
@RenderSection("Scripts", required: false)
|
||||
|
||||
<script type="text/javascript">
|
||||
var errCount = [];
|
||||
var interval;
|
||||
var loadWidgets = function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$("#ticker-widgets").load('@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/TickerWidgets', '', function (responseText, textStatus, XMLHttpRequest) {
|
||||
if (textStatus == 'error') {
|
||||
errCount["TickerWidgets"]++;
|
||||
if (errCount["TickerWidgets"] > 2) {
|
||||
$.Notification.notify('error', 'top left', 'Ticker update failed!', 'PTMagic Monitor failed to update data. If this error does not go away by itself, please check the connection to your hosting PC.')
|
||||
}
|
||||
} else if (responseText == 'returntologin') {
|
||||
window.location.replace("@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)Login");
|
||||
} else {
|
||||
errCount["TickerWidgets"] = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
errCount["TickerWidgets"] = 0;
|
||||
loadWidgets();
|
||||
interval = setInterval(function () { loadWidgets(); }, 5 * 1000);
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
$('.text-autocolor').autocolor(false);
|
||||
$('.text-autocolor-saw').autocolor(true);
|
||||
});
|
||||
</script>
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.core.js?v=@Html.Raw(Model.CurrentBotVersion)"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.app.js?v=@Html.Raw(Model.CurrentBotVersion)"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="A fully featured admin theme which can be used to build CRM, CMS, etc.">
|
||||
<meta name="author" content="Coderthemes">
|
||||
|
||||
<link rel="shortcut icon" href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/images/favicon_1.ico">
|
||||
|
||||
<title>PT Magic Monitor @ViewData["Title"]</title>
|
||||
|
||||
@RenderSection("Styles", required: false)
|
||||
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/icons.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/style.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/css/custom.css?v=@Html.Raw(Model.CurrentBotVersion)" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/modernizr.min.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<div class="account-pages"></div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="wrapper-page">
|
||||
|
||||
@RenderBody()
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var resizefunc = [];
|
||||
</script>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/popper.min.js"></script><!-- Popper for Bootstrap -->
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/bootstrap.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.slimscroll.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.scrollTo.min.js"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/custom.js?v=@Html.Raw(Model.CurrentBotVersion)"></script>
|
||||
|
||||
@RenderSection("Scripts", required: false)
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.core.js?v=@Html.Raw(Model.CurrentBotVersion)"></script>
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.app.js?v=@Html.Raw(Model.CurrentBotVersion)"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<environment include="Development">
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
|
||||
</environment>
|
||||
<environment exclude="Development">
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k">
|
||||
</script>
|
||||
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
|
||||
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH">
|
||||
</script>
|
||||
</environment>
|
|
@ -0,0 +1,3 @@
|
|||
@using Monitor
|
||||
@namespace Monitor.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
@page
|
||||
@model BagDetailsModel
|
||||
@{
|
||||
Layout = null;
|
||||
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = null;
|
||||
|
||||
if (Model.Summary.MarketSummary.ContainsKey(Model.DCALogData.Market)) {
|
||||
mps = Model.Summary.MarketSummary[Model.DCALogData.Market];
|
||||
}
|
||||
int nextBuyTrigger = Model.DCALogData.BoughtTimes + 1;
|
||||
|
||||
double nextBuyTriggerPercent = 0;
|
||||
if (Model.Summary.DCATriggers.Count >= nextBuyTrigger) {
|
||||
nextBuyTriggerPercent = Model.Summary.DCATriggers[nextBuyTrigger];
|
||||
}
|
||||
|
||||
bool buyDisabled = false;
|
||||
string buyTriggerText = Model.DCALogData.BuyTriggerPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
|
||||
|
||||
if (mps != null) {
|
||||
if (!mps.IsDCAEnabled) {
|
||||
nextBuyTriggerPercent = 0;
|
||||
buyDisabled = true;
|
||||
buyTriggerText = "";
|
||||
} else {
|
||||
if (Model.DCALogData.BuyTriggerPercent == 0) {
|
||||
Model.DCALogData.BuyTriggerPercent = Model.DCALogData.ProfitPercent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buyDisabled = Model.DCALogData.BuyStrategy == null;
|
||||
if (Model.DCALogData.BuyTriggerPercent == 0) {
|
||||
Model.DCALogData.BuyTriggerPercent = Model.DCALogData.ProfitPercent;
|
||||
}
|
||||
}
|
||||
|
||||
bool isTrailingBuyActive = Model.DCALogData.IsTrailing;
|
||||
if (Model.DCALogData.BuyStrategies.Count > 0) {
|
||||
isTrailingBuyActive = (Model.DCALogData.BuyStrategies.FindAll(bs => !bs.IsTrailing).Count == 0);
|
||||
}
|
||||
|
||||
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, Model.DCALogData.BuyStrategies, Model.DCALogData.BuyStrategy, isTrailingBuyActive);
|
||||
if (!Core.ProfitTrailer.StrategyHelper.IsValidStrategy(buyStrategyText, true)) {
|
||||
buyDisabled = true;
|
||||
}
|
||||
|
||||
string currentBuyValueText = Core.ProfitTrailer.StrategyHelper.GetCurrentValueText(Model.DCALogData.BuyStrategies, Model.DCALogData.BuyStrategy, Model.DCALogData.CurrentLowBBValue, Model.DCALogData.BuyTriggerPercent, true);
|
||||
string triggerBuyValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, Model.DCALogData.BuyStrategies, Model.DCALogData.BuyStrategy, Model.DCALogData.BBTrigger, Core.Main.Constants.MinTrendChange, Model.DCALogData.BoughtTimes, true);
|
||||
|
||||
string bagAgeText = "";
|
||||
int bagAgeSeconds = (int)Math.Ceiling(Model.DateTimeNow.Subtract(Model.DCALogData.FirstBoughtDate).TotalSeconds);
|
||||
bagAgeText = Core.Helper.SystemHelper.GetProperDurationTime(bagAgeSeconds);
|
||||
|
||||
double nextBuyTriggerPrice = 0.0;
|
||||
double nextAvgBoughtPrice = 0.0;
|
||||
if (nextBuyTriggerPercent != 0) {
|
||||
nextBuyTriggerPrice = Model.DCALogData.AverageBuyPrice + (Model.DCALogData.AverageBuyPrice * nextBuyTriggerPercent / 100);
|
||||
nextAvgBoughtPrice = (Model.DCALogData.TotalCost + (Model.DCALogData.Amount * nextBuyTriggerPrice)) / (Model.DCALogData.Amount * 2);
|
||||
}
|
||||
|
||||
bool isTrailingSellActive = false;
|
||||
if (Model.DCALogData.SellStrategies.Count > 0) {
|
||||
isTrailingSellActive = (Model.DCALogData.SellStrategies.FindAll(ss => !ss.IsTrailing).Count == 0);
|
||||
}
|
||||
|
||||
string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, Model.DCALogData.SellStrategies, Model.DCALogData.SellStrategy, isTrailingSellActive);
|
||||
string currentSellValueText = Core.ProfitTrailer.StrategyHelper.GetCurrentValueText(Model.DCALogData.SellStrategies, Model.DCALogData.SellStrategy, Model.DCALogData.CurrentHighBBValue, Model.DCALogData.ProfitPercent, true);
|
||||
string triggerSellValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, Model.DCALogData.SellStrategies, Model.DCALogData.SellStrategy, Model.DCALogData.BBTrigger, Model.DCALogData.SellTrigger, 0, true);
|
||||
|
||||
double sellTriggerPrice = Model.DCALogData.AverageBuyPrice + (Model.DCALogData.AverageBuyPrice * Model.DCALogData.SellTrigger / 100);
|
||||
|
||||
double averageProfitPercent = 0;
|
||||
if (Model.PTData.SellLog.FindAll(m => m.Market == Model.DCALogData.Market).Count > 0) {
|
||||
averageProfitPercent = Model.PTData.SellLog.FindAll(m => m.Market == Model.DCALogData.Market).Average(p => p.ProfitPercent);
|
||||
}
|
||||
|
||||
double investedFiatValue = Math.Round(Model.DCALogData.TotalCost * Model.Summary.MainMarketPrice, 2);
|
||||
double currentValue = Math.Round(Model.DCALogData.Amount * Model.DCALogData.CurrentPrice, 8);
|
||||
double currentFiatValue = Math.Round(Model.DCALogData.Amount * Model.DCALogData.CurrentPrice * Model.Summary.MainMarketPrice, 2);
|
||||
double currentProfit = Math.Round(currentValue - Model.DCALogData.TotalCost, 8);
|
||||
double currentFiatProfit = Math.Round(currentFiatValue - investedFiatValue, 2);
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title mt-0">Bag details for @Model.DCAMarket</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<!-- TradingView Widget BEGIN -->
|
||||
<div class="tradingview-widget-container">
|
||||
<div id="tradingview_6aa22" style="height:300px;"></div>
|
||||
<div class="tradingview-widget-copyright"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, Model.DCAMarket, Model.Summary.MainMarket)" rel="noopener" target="_blank"><span class="blue-text">@Model.DCAMarket</span> <span class="blue-text">chart</span> by TradingView</a></div>
|
||||
</div>
|
||||
<!-- TradingView Widget END -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-12"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h4 class="m-t-0 header-title">Buy Details at DCA Level @Model.DCALogData.BoughtTimes</h4>
|
||||
|
||||
<table class="table table-sm table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>First Bought</td>
|
||||
<td class="text-right">@Model.DCALogData.FirstBoughtDate.ToShortDateString() @Model.DCALogData.FirstBoughtDate.ToShortTimeString()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DCA Buy Strategies</td>
|
||||
@if (buyDisabled) {
|
||||
<td class="text-right text-nowrap">@Html.Raw(buyStrategyText)</td>
|
||||
} else {
|
||||
<td class="text-right text-nowrap">@Html.Raw(buyStrategyText)</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Average Buy Price</td>
|
||||
<td class="text-right">@Model.DCALogData.AverageBuyPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Invested @Model.Summary.MainMarket Value</td>
|
||||
<td class="text-right">@Model.DCALogData.TotalCost.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Invested @Model.Summary.MainFiatCurrency Value</td>
|
||||
<td class="text-right">@Html.Raw(Model.MainFiatCurrencySymbol + investedFiatValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current Buy Trigger Value</td>
|
||||
<td class="text-right">
|
||||
@if (!buyDisabled) {
|
||||
@Html.Raw(currentBuyValueText)
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Next Buy Trigger</td>
|
||||
@if (!buyDisabled && Model.DCALogData.BoughtTimes < Model.Summary.DCALevels) {
|
||||
<td class="text-right text-nowrap">@Html.Raw(triggerBuyValueText)</td>
|
||||
} else {
|
||||
if (!buyDisabled && Model.DCALogData.BoughtTimes >= Model.Summary.DCALevels) {
|
||||
<td class="text-right text-warning">MAX</td>
|
||||
} else {
|
||||
<td class="text-right text-warning">Disabled!</td>
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
@if (Model.DCALogData.BuyStrategies.Count == 0) {
|
||||
<tr>
|
||||
<td>Next Buy Trigger Price <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Approximate price for the next buy trigger to be matched. Not taking any trailing into account!"></i></td>
|
||||
@if (!buyDisabled) {
|
||||
@if (nextBuyTriggerPrice != 0) {
|
||||
<td class="text-right">@nextBuyTriggerPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
} else {
|
||||
<td class="text-right text-warning">MAX</td>
|
||||
}
|
||||
} else {
|
||||
<td class="text-right text-warning">DCA Disabled!</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Next approx. Avg. Price <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Approximate new average bought price for this market AFTER the next buy level got bought. Not taking any trailing into account!"></i></td>
|
||||
@if (!buyDisabled) {
|
||||
@if (nextAvgBoughtPrice != 0) {
|
||||
<td class="text-right">@nextAvgBoughtPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
} else {
|
||||
<td class="text-right text-warning">MAX</td>
|
||||
}
|
||||
} else {
|
||||
<td class="text-right text-warning">DCA Disabled!</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4 class="m-t-0 header-title">Sell Details</h4>
|
||||
|
||||
<table class="table table-sm table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Age</td>
|
||||
<td class="text-right">@bagAgeText</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sell Strategies</td>
|
||||
<td class="text-right">@Html.Raw(sellStrategyText)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current Bid Price</td>
|
||||
<td class="text-right">@Model.DCALogData.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current @Model.Summary.MainMarket Value</td>
|
||||
<td class="text-right">@currentValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US")) (<span class="text-autocolor">@currentProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</span>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current @Model.Summary.MainFiatCurrency Value</td>
|
||||
<td class="text-right">@Html.Raw(Model.MainFiatCurrencySymbol + currentFiatValue.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))) (<span class="text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + currentFiatProfit.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</span>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current Profit/Loss</td>
|
||||
<td class="text-right text-autocolor">@Model.DCALogData.ProfitPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
@if (Model.DCALogData.CurrentHighBBValue > 0) {
|
||||
<tr>
|
||||
<td>Sell Value (HBB)</td>
|
||||
<td class="text-right">@Model.DCALogData.CurrentHighBBValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td>Sell Trigger</td>
|
||||
<td class="text-right">@Html.Raw(triggerSellValueText)</td>
|
||||
</tr>
|
||||
@if (Model.DCALogData.SellStrategies.Count == 0) {
|
||||
<tr>
|
||||
<td>Sell Trigger Price <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Approximate price needed for the sell trigger to be matched. Not taking any trailing into account!"></i></td>
|
||||
<td class="text-right">@sellTriggerPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td>Average Profit % <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="The average profit percentage you achieved in sales for @Model.DCALogData.Market according to your sales log"></i></td>
|
||||
@if (averageProfitPercent != 0) {
|
||||
<td class="text-right text-autocolor">@averageProfitPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
} else {
|
||||
<td class="text-right text-muted">No sales found for @Model.DCALogData.Market</td>
|
||||
}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
new TradingView.widget(
|
||||
{
|
||||
"autosize":true,
|
||||
"symbol": "@Core.Helper.SystemHelper.GetTradingViewSymbol(Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, Model.DCAMarket, Model.Summary.MainMarket)",
|
||||
"interval": "60",
|
||||
"timezone": "exchange",
|
||||
"theme": "Dark",
|
||||
"style": "1",
|
||||
"locale": "en",
|
||||
"toolbar_bg": "#f1f3f6",
|
||||
"enable_publishing": false,
|
||||
"allow_symbol_change": false,
|
||||
"hideideas": true,
|
||||
"hide_side_toolbar": false,
|
||||
"container_id": "tradingview_6aa22"
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class BagDetailsModel : _Internal.BasePageModelSecure {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public string DCAMarket = "";
|
||||
public DCALogData DCALogData = null;
|
||||
public DateTimeOffset DateTimeNow = Constants.confMinDate;
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
DCAMarket = GetStringParameter("m", "");
|
||||
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
|
||||
DCALogData = PTData.DCALog.Find(d => d.Market == DCAMarket);
|
||||
|
||||
// Convert local offset time to UTC
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
@page
|
||||
@model BagListModel
|
||||
@{
|
||||
Layout = null;
|
||||
|
||||
System.Reflection.PropertyInfo propertyInfo = typeof(Core.Main.DataObjects.PTMagicData.DCALogData).GetProperty(Model.SortFieldId);
|
||||
List<Core.Main.DataObjects.PTMagicData.DCALogData> dcaLogResult = Model.PTData.DCALog.OrderByDescending(d => propertyInfo.GetValue(d, null)).ToList();
|
||||
if (Model.SortDirection.Equals("ASC")) {
|
||||
dcaLogResult = Model.PTData.DCALog.OrderBy(d => propertyInfo.GetValue(d, null)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.DCALogData dcaLogEntry in dcaLogResult) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = null;
|
||||
if (Model.Summary.MarketSummary.ContainsKey(dcaLogEntry.Market)) {
|
||||
mps = Model.Summary.MarketSummary[dcaLogEntry.Market];
|
||||
}
|
||||
int nextBuyTrigger = dcaLogEntry.BoughtTimes + 1;
|
||||
|
||||
double nextBuyTriggerPercent = 0;
|
||||
if (Model.Summary.DCATriggers.Count >= nextBuyTrigger) {
|
||||
nextBuyTriggerPercent = Model.Summary.DCATriggers[nextBuyTrigger];
|
||||
}
|
||||
|
||||
bool buyDisabled = false;
|
||||
string buyTriggerText = dcaLogEntry.BuyTriggerPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
|
||||
if (mps != null) {
|
||||
if (!mps.IsDCAEnabled) {
|
||||
nextBuyTriggerPercent = 0;
|
||||
buyDisabled = true;
|
||||
buyTriggerText = "";
|
||||
} else {
|
||||
if (dcaLogEntry.BuyTriggerPercent == 0) {
|
||||
dcaLogEntry.BuyTriggerPercent = dcaLogEntry.ProfitPercent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buyDisabled = dcaLogEntry.BuyStrategy == null;
|
||||
if (dcaLogEntry.BuyTriggerPercent == 0) {
|
||||
dcaLogEntry.BuyTriggerPercent = dcaLogEntry.ProfitPercent;
|
||||
}
|
||||
}
|
||||
|
||||
bool isTrailingBuyActive = dcaLogEntry.IsTrailing;
|
||||
if (dcaLogEntry.BuyStrategies.Count > 0) {
|
||||
isTrailingBuyActive = (dcaLogEntry.BuyStrategies.FindAll(bs => !bs.IsTrailing).Count == 0);
|
||||
}
|
||||
|
||||
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, isTrailingBuyActive);
|
||||
if (!Core.ProfitTrailer.StrategyHelper.IsValidStrategy(buyStrategyText, true)) {
|
||||
buyDisabled = true;
|
||||
}
|
||||
|
||||
string currentBuyValueText = Core.ProfitTrailer.StrategyHelper.GetCurrentValueText(dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, dcaLogEntry.CurrentLowBBValue, dcaLogEntry.BuyTriggerPercent, true);
|
||||
string triggerBuyValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, dcaLogEntry.BBTrigger, Core.Main.Constants.MinTrendChange, dcaLogEntry.BoughtTimes, true);
|
||||
|
||||
bool isTrailingSellActive = false;
|
||||
if (dcaLogEntry.SellStrategies.Count > 0) {
|
||||
isTrailingSellActive = (dcaLogEntry.SellStrategies.FindAll(ss => !ss.IsTrailing).Count == 0);
|
||||
}
|
||||
|
||||
string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isTrailingSellActive);
|
||||
string currentSellValueText = Core.ProfitTrailer.StrategyHelper.GetCurrentValueText(dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, dcaLogEntry.CurrentHighBBValue, dcaLogEntry.ProfitPercent, true);
|
||||
string triggerSellValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, dcaLogEntry.BBTrigger, dcaLogEntry.SellTrigger, 0, true);
|
||||
|
||||
<tr>
|
||||
@if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a></th>
|
||||
} else {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
|
||||
}
|
||||
<td class="text-right">
|
||||
@if (dcaLogEntry.BoughtTimes > 0) {
|
||||
@dcaLogEntry.BoughtTimes;
|
||||
}
|
||||
</td>
|
||||
@if (buyDisabled) {
|
||||
<td class="text-nowrap">@Html.Raw(buyStrategyText)</td>
|
||||
} else {
|
||||
<td class="text-nowrap">@Html.Raw(buyStrategyText)</td>
|
||||
}
|
||||
<td class="text-right text-nowrap">
|
||||
@if (!buyDisabled) {
|
||||
@Html.Raw(currentBuyValueText)
|
||||
}
|
||||
</td>
|
||||
@if (!buyDisabled && dcaLogEntry.BoughtTimes < Model.Summary.DCALevels) {
|
||||
<td class="text-right text-nowrap">@Html.Raw(triggerBuyValueText)</td>
|
||||
} else {
|
||||
if (!buyDisabled && dcaLogEntry.BoughtTimes >= Model.Summary.DCALevels) {
|
||||
<td class="text-right text-warning">MAX</td>
|
||||
} else {
|
||||
<td class="text-right text-warning">Disabled!</td>
|
||||
}
|
||||
}
|
||||
<td class="text-right text-autocolor">@dcaLogEntry.ProfitPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
<td class="text-nowrap">@Html.Raw(sellStrategyText)</td>
|
||||
<td class="text-right text-nowrap">
|
||||
@Html.Raw(triggerSellValueText)
|
||||
</td>
|
||||
<td class="text-right">@dcaLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@dcaLogEntry.AverageBuyPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@dcaLogEntry.TotalCost.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/BagDetails/?m=@dcaLogEntry.Market" data-remote="false" data-toggle="modal" data-target="#dca-chart" class="btn btn-sm btn-ptmagic">Details</a></td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$("#dca-chart").on("show.bs.modal", function (e) {
|
||||
$(this).find(".modal-content").html('<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>');
|
||||
var link = $(e.relatedTarget);
|
||||
$(this).find(".modal-content").load(link.attr("href"), function () {
|
||||
$('.text-autocolor').autocolor(false);
|
||||
$('[role="tooltip"]').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class BagListModel : _Internal.BasePageModelSecureAJAX {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public string SortFieldId = "";
|
||||
public string SortDirection = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
SortFieldId = GetStringParameter("s", "ProfitPercent");
|
||||
SortDirection = GetStringParameter("d", "DESC");
|
||||
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
@page
|
||||
@model BuyListModel
|
||||
@{
|
||||
Layout = null;
|
||||
|
||||
System.Reflection.PropertyInfo propertyInfo = typeof(Core.Main.DataObjects.PTMagicData.BuyLogData).GetProperty(Model.SortFieldId);
|
||||
List<Core.Main.DataObjects.PTMagicData.BuyLogData> buyLogResult = Model.PTData.BuyLog.OrderByDescending(d => propertyInfo.GetValue(d, null)).ToList();
|
||||
if (Model.SortDirection.Equals("ASC")) {
|
||||
buyLogResult = Model.PTData.BuyLog.OrderBy(d => propertyInfo.GetValue(d, null)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.BuyLogData buyLogEntry in buyLogResult) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = null;
|
||||
if (Model.Summary.MarketSummary.ContainsKey(buyLogEntry.Market)) {
|
||||
mps = Model.Summary.MarketSummary[buyLogEntry.Market];
|
||||
}
|
||||
|
||||
bool isTrailingBuyActive = buyLogEntry.IsTrailing;
|
||||
if (buyLogEntry.BuyStrategies.Count > 0) {
|
||||
isTrailingBuyActive = (buyLogEntry.BuyStrategies.FindAll(bs => !bs.IsTrailing).Count == 0);
|
||||
}
|
||||
|
||||
bool buyDisabled = false;
|
||||
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, isTrailingBuyActive);
|
||||
if (!Core.ProfitTrailer.StrategyHelper.IsValidStrategy(buyStrategyText, true)) {
|
||||
buyDisabled = true;
|
||||
}
|
||||
|
||||
string currentValueText = Core.ProfitTrailer.StrategyHelper.GetCurrentValueText(buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, buyLogEntry.CurrentLowBBValue, buyLogEntry.CurrentValue, true);
|
||||
string triggerValueText = Core.ProfitTrailer.StrategyHelper.GetTriggerValueText(Model.Summary, buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, buyLogEntry.BBTrigger, buyLogEntry.TriggerValue, 0, true);
|
||||
<tr>
|
||||
@if (mps != null && (mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0)) {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a></th>
|
||||
} else {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
|
||||
}
|
||||
@if (buyDisabled) {
|
||||
<td>@Html.Raw(buyStrategyText)</td>
|
||||
} else {
|
||||
<td>@Html.Raw(buyStrategyText)</td>
|
||||
}
|
||||
<td class="text-right">@Html.Raw(currentValueText)</td>
|
||||
<td class="text-right">@Html.Raw(triggerValueText)</td>
|
||||
<td class="text-right">@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class BuyListModel : _Internal.BasePageModelSecureAJAX {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public string SortFieldId = "";
|
||||
public string SortDirection = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
SortFieldId = GetStringParameter("s", "ProfitPercent");
|
||||
SortDirection = GetStringParameter("d", "DESC");
|
||||
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
@page
|
||||
@model DashboardBottomModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 m-b-20 header-title"><b>Sales Overview</b><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)SalesAnalyzer">more</a></small></h4>
|
||||
@{
|
||||
double totalProfit = Model.PTData.SellLog.Sum(s => s.Profit);
|
||||
double totalProfitFiat = Math.Round(totalProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double percentGain = Math.Round(totalProfit / Model.PTMagicConfiguration.GeneralSettings.Application.StartBalance * 100, 2);
|
||||
string percentGainText = percentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")) + "%";
|
||||
if (Model.PTData.TransactionData.Transactions.Count > 0) {
|
||||
percentGainText = "<i class=\"fa fa-info-circle text-muted\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"You have added at least one manual transaction, so the total gain percentage cannot be calculated.\"></i>";
|
||||
}
|
||||
|
||||
double todaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime);
|
||||
double todaysProfit = Model.PTData.SellLogToday.Sum(s => s.Profit);
|
||||
double todaysProfitFiat = Math.Round(todaysProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double todaysPercentGain = Math.Round(todaysProfit / todaysStartBalance * 100, 2);
|
||||
|
||||
double yesterdaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime.AddDays(-1));
|
||||
double yesterdaysProfit = Model.PTData.SellLogYesterday.Sum(s => s.Profit);
|
||||
double yesterdaysProfitFiat = Math.Round(yesterdaysProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double yesterdaysPercentGain = Math.Round(yesterdaysProfit / yesterdaysStartBalance * 100, 2);
|
||||
|
||||
double last7DaysStartBalance = Model.PTData.GetSnapshotBalance(Model.DateTimeNow.DateTime.AddDays(-7));
|
||||
double last7DaysProfit = Model.PTData.SellLogLast7Days.Sum(s => s.Profit);
|
||||
double last7DaysProfitFiat = Math.Round(last7DaysProfit * Model.Summary.MainMarketPrice, 2);
|
||||
double last7DaysPercentGain = Math.Round(last7DaysProfit / last7DaysStartBalance * 100, 2);
|
||||
}
|
||||
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-right">Sales</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainMarket</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
|
||||
<th class="text-right">% Gain</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Today</th>
|
||||
<td class="text-right">@Model.PTData.SellLogToday.Count</td>
|
||||
<td class="text-right text-autocolor">@todaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + todaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@todaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Yesterday</th>
|
||||
<td class="text-right">@Model.PTData.SellLogYesterday.Count</td>
|
||||
<td class="text-right text-autocolor">@yesterdaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + yesterdaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@yesterdaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last 7 Days</th>
|
||||
<td class="text-right">@Model.PTData.SellLogLast7Days.Count</td>
|
||||
<td class="text-right text-autocolor">@last7DaysProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + last7DaysProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@last7DaysPercentGain.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total</th>
|
||||
<td class="text-right">@Model.PTData.SellLog.Count</td>
|
||||
<td class="text-right text-autocolor">@totalProfit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(Model.MainFiatCurrencySymbol + totalProfitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))</td>
|
||||
<td class="text-right text-autocolor">@Html.Raw(percentGainText)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 m-b-20 header-title"><b>Market Trends</b><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)MarketAnalyzer">more</a></small></h4>
|
||||
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="text-right">Markets</th>
|
||||
<th class="text-right">Timeframe</th>
|
||||
<th class="text-right">Change</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var marketTrend in Model.MarketTrends.OrderBy(mt => mt.TrendMinutes)) {
|
||||
if (Model.Summary.MarketTrendChanges.ContainsKey(marketTrend.Name)) {
|
||||
double trendChange = Model.Summary.MarketTrendChanges[marketTrend.Name].OrderByDescending(mtc => mtc.TrendDateTime).First().TrendChange;
|
||||
string trendChangeOutput = trendChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
|
||||
int marketCount = marketTrend.MaxMarkets;
|
||||
string marketCountString = marketCount.ToString();
|
||||
|
||||
if (marketCount == 0) {
|
||||
marketCountString = "All";
|
||||
} else if (marketCount > Model.Summary.MarketSummary.Keys.Count && marketTrend.Platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
marketCountString = Model.Summary.MarketSummary.Keys.Count.ToString();
|
||||
}
|
||||
<tr>
|
||||
<td>@Core.Helper.SystemHelper.SplitCamelCase(marketTrend.Name)</td>
|
||||
<td class="text-right">@marketCountString</td>
|
||||
<td class="text-right">@Core.Helper.SystemHelper.GetProperDurationTime(marketTrend.TrendMinutes * 60, false)</td>
|
||||
<td class="text-right text-autocolor">@trendChangeOutput%</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box" style="height:205px;">
|
||||
@if (!Model.ProfitChartDataJSON.Equals("")) {
|
||||
<div class="profit-chart">
|
||||
<svg style="height:205px;width:100%"></svg>
|
||||
</div>
|
||||
} else {
|
||||
<p>Not able to load graph, no sales data found.<br />If you still do not see a graph after you made your first sale, report an issue.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card-box" style="height:205px;">
|
||||
@if (!Model.TrendChartDataJSON.Equals("")) {
|
||||
<div class="trend-chart">
|
||||
<svg style="height:205px;width:100%"></svg>
|
||||
</div>
|
||||
} else {
|
||||
<p>Not able to load graph, no market trend data found.<br />If you still do not see a graph after more than hour, report an issue.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function ($) {
|
||||
'use strict';
|
||||
$('[role="tooltip"]').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
|
||||
@if (!Model.Summary.CurrentGlobalSetting.SettingName.Equals(Model.LastGlobalSetting)) {
|
||||
<text>
|
||||
$.Notification.notify('success', 'top left', '@Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.CurrentGlobalSetting.SettingName) now active!', 'PTMagic switched Profit Trailer settings to "@Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.CurrentGlobalSetting.SettingName)".');
|
||||
</text>
|
||||
}
|
||||
|
||||
@if (!Model.TrendChartDataJSON.Equals("")) {
|
||||
<text>
|
||||
nv.addGraph(function () {
|
||||
var lineChart = nv.models.lineChart();
|
||||
var height = 300;
|
||||
var chartData = @Html.Raw(Model.TrendChartDataJSON);
|
||||
lineChart.useInteractiveGuideline(true);
|
||||
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%H:%M')(new Date(d)); });
|
||||
lineChart.yAxis.axisLabel('Trend %').tickFormat(d3.format(',.2f'));
|
||||
d3.select('.trend-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
|
||||
//nv.utils.windowResize(lineChart.update); v1.3.0 => Removed this line to prevent memory leak
|
||||
|
||||
return lineChart;
|
||||
});
|
||||
</text>
|
||||
}
|
||||
|
||||
@if (!Model.ProfitChartDataJSON.Equals("")) {
|
||||
<text>
|
||||
nv.addGraph(function () {
|
||||
var lineChart = nv.models.lineChart();
|
||||
var height = 300;
|
||||
var chartData = @Html.Raw(Model.ProfitChartDataJSON);
|
||||
lineChart.useInteractiveGuideline(true);
|
||||
lineChart.xAxis.tickFormat(function (d) { return d3.time.format('%Y/%m/%d')(new Date(d)); });
|
||||
lineChart.yAxis.axisLabel('Profit').tickFormat(d3.format(',.2f'));
|
||||
d3.select('.profit-chart svg').attr('perserveAspectRatio', 'xMinYMid').datum(chartData).transition().duration(500).call(lineChart);
|
||||
//nv.utils.windowResize(lineChart.update); v1.3.0 => Removed this line to prevent memory leak
|
||||
|
||||
return lineChart;
|
||||
});
|
||||
</text>
|
||||
}
|
||||
})(jQuery);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,144 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class DashboardBottomModel : _Internal.BasePageModelSecureAJAX {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public List<MarketTrend> MarketTrends { get; set; } = new List<MarketTrend>();
|
||||
public string TrendChartDataJSON = "";
|
||||
public string ProfitChartDataJSON = "";
|
||||
public string LastGlobalSetting = "Default";
|
||||
public DateTimeOffset DateTimeNow = Constants.confMinDate;
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
|
||||
// Cleanup temp files
|
||||
FileHelper.CleanupFilesMinutes(PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar, 5);
|
||||
|
||||
// Convert local offset time to UTC
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
|
||||
|
||||
// Get last and current active setting
|
||||
if (!String.IsNullOrEmpty(HttpContext.Session.GetString("LastGlobalSetting"))) {
|
||||
LastGlobalSetting = HttpContext.Session.GetString("LastGlobalSetting");
|
||||
}
|
||||
HttpContext.Session.SetString("LastGlobalSetting", Summary.CurrentGlobalSetting.SettingName);
|
||||
|
||||
// Get market trends
|
||||
MarketTrends = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.OrderBy(mt => mt.TrendMinutes).ThenByDescending(mt => mt.Platform).ToList();
|
||||
|
||||
BuildMarketTrendChartData();
|
||||
BuildProfitChartData();
|
||||
}
|
||||
|
||||
private void BuildMarketTrendChartData() {
|
||||
if (MarketTrends.Count > 0) {
|
||||
TrendChartDataJSON = "[";
|
||||
int mtIndex = 0;
|
||||
foreach (MarketTrend mt in MarketTrends) {
|
||||
if (mt.DisplayGraph) {
|
||||
string lineColor = "";
|
||||
if (mtIndex < Constants.ChartLineColors.Length) {
|
||||
lineColor = Constants.ChartLineColors[mtIndex];
|
||||
} else {
|
||||
lineColor = Constants.ChartLineColors[mtIndex - 20];
|
||||
}
|
||||
|
||||
if (Summary.MarketTrendChanges.ContainsKey(mt.Name)) {
|
||||
List<MarketTrendChange> marketTrendChangeSummaries = Summary.MarketTrendChanges[mt.Name];
|
||||
|
||||
if (marketTrendChangeSummaries.Count > 0) {
|
||||
if (!TrendChartDataJSON.Equals("[")) TrendChartDataJSON += ",";
|
||||
|
||||
TrendChartDataJSON += "{";
|
||||
TrendChartDataJSON += "key: '" + SystemHelper.SplitCamelCase(mt.Name) + "',";
|
||||
TrendChartDataJSON += "color: '" + lineColor + "',";
|
||||
TrendChartDataJSON += "values: [";
|
||||
|
||||
// Get trend ticks for chart
|
||||
DateTime currentDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, 0, 0);
|
||||
DateTime startDateTime = currentDateTime.AddHours(-PTMagicConfiguration.GeneralSettings.Monitor.GraphMaxTimeframeHours);
|
||||
DateTime endDateTime = currentDateTime;
|
||||
int trendChartTicks = 0;
|
||||
for (DateTime tickTime = startDateTime; tickTime <= endDateTime; tickTime = tickTime.AddMinutes(PTMagicConfiguration.GeneralSettings.Monitor.GraphIntervalMinutes)) {
|
||||
List<MarketTrendChange> tickRange = marketTrendChangeSummaries.FindAll(m => m.TrendDateTime >= tickTime).OrderBy(m => m.TrendDateTime).ToList();
|
||||
if (tickRange.Count > 0) {
|
||||
MarketTrendChange mtc = tickRange.First();
|
||||
if (tickTime != startDateTime) TrendChartDataJSON += ",\n";
|
||||
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
|
||||
|
||||
TrendChartDataJSON += "{ x: new Date('" + tickTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
|
||||
trendChartTicks++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add most recent tick
|
||||
List<MarketTrendChange> latestTickRange = marketTrendChangeSummaries.OrderByDescending(m => m.TrendDateTime).ToList();
|
||||
if (latestTickRange.Count > 0) {
|
||||
MarketTrendChange mtc = latestTickRange.First();
|
||||
if (trendChartTicks > 0) TrendChartDataJSON += ",\n";
|
||||
if (Double.IsInfinity(mtc.TrendChange)) mtc.TrendChange = 0;
|
||||
|
||||
TrendChartDataJSON += "{ x: new Date('" + mtc.TrendDateTime.ToString("yyyy-MM-ddTHH:mm:ss").Replace(".", ":") + "'), y: " + mtc.TrendChange.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
|
||||
}
|
||||
|
||||
TrendChartDataJSON += "]";
|
||||
TrendChartDataJSON += "}";
|
||||
|
||||
mtIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TrendChartDataJSON += "]";
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildProfitChartData() {
|
||||
int tradeDayIndex = 0;
|
||||
string profitPerDayJSON = "";
|
||||
if (PTData.SellLog.Count > 0) {
|
||||
DateTime minSellLogDate = PTData.SellLog.OrderBy(sl => sl.SoldDate).First().SoldDate.Date;
|
||||
DateTime graphStartDate = DateTime.Now.Date.AddDays(-30);
|
||||
if (minSellLogDate > graphStartDate) graphStartDate = minSellLogDate;
|
||||
for (DateTime salesDate = graphStartDate; salesDate <= DateTime.Now.Date; salesDate = salesDate.AddDays(1)) {
|
||||
if (tradeDayIndex > 0) {
|
||||
profitPerDayJSON += ",\n";
|
||||
}
|
||||
|
||||
int trades = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate).Count;
|
||||
double profit = PTData.SellLog.FindAll(t => t.SoldDate.Date == salesDate).Sum(t => t.Profit);
|
||||
double profitFiat = Math.Round(profit * Summary.MainMarketPrice, 2);
|
||||
|
||||
profitPerDayJSON += "{x: new Date('" + salesDate.ToString("yyyy-MM-dd") + "'), y: " + profitFiat.ToString("0.00", new System.Globalization.CultureInfo("en-US")) + "}";
|
||||
|
||||
tradeDayIndex++;
|
||||
}
|
||||
|
||||
ProfitChartDataJSON = "[";
|
||||
ProfitChartDataJSON += "{";
|
||||
ProfitChartDataJSON += "key: 'Profit in " + Summary.MainFiatCurrency + "',";
|
||||
ProfitChartDataJSON += "color: '" + Constants.ChartLineColors[1] + "',";
|
||||
ProfitChartDataJSON += "values: [" + profitPerDayJSON + "]";
|
||||
ProfitChartDataJSON += "}";
|
||||
ProfitChartDataJSON += "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
@page
|
||||
@model DashboardTopModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 m-b-20 header-title"><b>Possible Buys (@Model.PTData.BuyLog.Count)</b><small id="buylist-refresh-icon"></small><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BuyAnalyzer">more</a></small></h4>
|
||||
@if (Model.PTData.BuyLog.Count == 0) {
|
||||
|
||||
<p>Your Profit Trailer did not find anything worth buying so far.</p>
|
||||
|
||||
} else {
|
||||
<table class="table table-sm m-b-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Market</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Current profit percentage">Buy Strategies</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Current ask price for this market">Ask Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.BuyLogData buyLogEntry in Model.PTData.BuyLog.OrderByDescending(b => b.IsTrailing).ThenBy(b => b.CurrentValue).Take(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries)) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = null;
|
||||
if (Model.Summary.MarketSummary.ContainsKey(buyLogEntry.Market)) {
|
||||
mps = Model.Summary.MarketSummary[buyLogEntry.Market];
|
||||
}
|
||||
|
||||
bool isTrailingBuyActive = buyLogEntry.IsTrailing;
|
||||
if (buyLogEntry.BuyStrategies.Count > 0) {
|
||||
isTrailingBuyActive = (buyLogEntry.BuyStrategies.FindAll(bs => !bs.IsTrailing) == null);
|
||||
}
|
||||
|
||||
bool buyDisabled = false;
|
||||
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, buyLogEntry.BuyStrategies, buyLogEntry.BuyStrategy, isTrailingBuyActive);
|
||||
if (!Core.ProfitTrailer.StrategyHelper.IsValidStrategy(buyStrategyText, true)) {
|
||||
buyDisabled = true;
|
||||
}
|
||||
|
||||
<tr>
|
||||
@if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a></th>
|
||||
} else {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, buyLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@buyLogEntry.Market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
|
||||
}
|
||||
@if (buyDisabled) {
|
||||
<td>@Html.Raw(buyStrategyText)</td>
|
||||
} else {
|
||||
<td>@Html.Raw(buyStrategyText)</td>
|
||||
}
|
||||
<td class="text-right">@buyLogEntry.CurrentPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if (Model.PTData.BuyLog.Count > Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries) {
|
||||
<p class="text-right"><small><i class="fa fa-info-circle"></i> @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBuyEntries of @Model.PTData.BuyLog.Count items listed - <a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BuyAnalyzer">View all items</a></small></p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card-box">
|
||||
<h4 class="m-t-0 m-b-20 header-title"><b>Pairs & DCA (@Model.PTData.DCALog.Count)</b><small id="baglist-refresh-icon"></small><small class="pull-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BagAnalyzer">more</a></small></h4>
|
||||
|
||||
@if (Model.PTData.DCALog.Count == 0) {
|
||||
|
||||
<p>Your Profit Trailer did not buy anything so far that's worth analyzing.</p>
|
||||
|
||||
} else {
|
||||
|
||||
<table class="table table-sm m-b-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Market</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Current DCA level">DCA</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Active buy strategies">Buy Strats</th>
|
||||
<th data-toggle="tooltip" data-placement="top" title="Active sell strategies">Sell Strats</th>
|
||||
<th class="text-right" data-toggle="tooltip" data-placement="top" title="Current profit percentage">Profit</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.DCALogData dcaLogEntry in Model.PTData.DCALog.OrderByDescending(d => d.ProfitPercent).Take(Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBagEntries)) {
|
||||
Core.Main.DataObjects.PTMagicData.MarketPairSummary mps = null;
|
||||
if (Model.Summary.MarketSummary.ContainsKey(dcaLogEntry.Market)) {
|
||||
mps = Model.Summary.MarketSummary[dcaLogEntry.Market];
|
||||
}
|
||||
|
||||
bool dcaEnabled = true;
|
||||
if (mps != null) {
|
||||
dcaEnabled = mps.IsDCAEnabled;
|
||||
}
|
||||
|
||||
bool isTrailingBuyActive = dcaLogEntry.IsTrailing;
|
||||
if (dcaLogEntry.BuyStrategies.Count > 0) {
|
||||
isTrailingBuyActive = (dcaLogEntry.BuyStrategies.FindAll(bs => !bs.IsTrailing) == null);
|
||||
}
|
||||
|
||||
bool isTrailingSellActive = false;
|
||||
if (dcaLogEntry.SellStrategies.Count > 0) {
|
||||
isTrailingSellActive = (dcaLogEntry.SellStrategies.FindAll(ss => !ss.IsTrailing) == null);
|
||||
}
|
||||
|
||||
bool buyDisabled = false;
|
||||
string buyStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.BuyStrategies, dcaLogEntry.BuyStrategy, isTrailingBuyActive);
|
||||
if (!Core.ProfitTrailer.StrategyHelper.IsValidStrategy(buyStrategyText, true)) {
|
||||
buyDisabled = true;
|
||||
}
|
||||
|
||||
string sellStrategyText = Core.ProfitTrailer.StrategyHelper.GetStrategyText(Model.Summary, dcaLogEntry.SellStrategies, dcaLogEntry.SellStrategy, isTrailingSellActive);
|
||||
|
||||
<tr>
|
||||
@if (mps == null || mps.ActiveSingleSettings == null || mps.ActiveSingleSettings.Count == 0) {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a></th>
|
||||
} else {
|
||||
<th class="align-top"><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, dcaLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@dcaLogEntry.Market</a> <i class="fa fa-exclamation-triangle text-highlight" data-toggle="tooltip" data-placement="top" data-html="true" title="@await Component.InvokeAsync("PairIcon", mps)" data-template="<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner pair-tooltip'></div></div>"></i></th>
|
||||
}
|
||||
<td class="text-right">
|
||||
@if (dcaEnabled) {
|
||||
@if (dcaLogEntry.BoughtTimes > 0) {
|
||||
@dcaLogEntry.BoughtTimes;
|
||||
}
|
||||
} else {
|
||||
<span class="text-warning" data-toggle="tooltip" data-placement="top" title="DCA is disabled for this market!">Disabled!</span>
|
||||
}
|
||||
</td>
|
||||
@if (buyDisabled) {
|
||||
<td>@Html.Raw(buyStrategyText)</td>
|
||||
} else {
|
||||
<td>@Html.Raw(buyStrategyText)</td>
|
||||
}
|
||||
<td>@Html.Raw(sellStrategyText)</td>
|
||||
<td class="text-right">
|
||||
@if (isTrailingSellActive) {
|
||||
<i class="fa fa-flag-checkered text-success" data-toggle="tooltip" data-placement="top" title="Trailing sell active!"></i>
|
||||
}
|
||||
<span class="text-autocolor">@dcaLogEntry.ProfitPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</span>
|
||||
</td>
|
||||
<td class="text-right"><a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)_get/BagDetails/?m=@dcaLogEntry.Market" data-remote="false" data-toggle="modal" data-target="#dca-chart" class="btn btn-mini btn-ptmagic"><i class="fa fa-plus"></i></a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if (Model.PTData.DCALog.Count > Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBagEntries) {
|
||||
<p class="text-right"><small><i class="fa fa-info-circle"></i> @Model.PTMagicConfiguration.GeneralSettings.Monitor.MaxDashboardBagEntries of @Model.PTData.DCALog.Count items listed - <a href="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)BagAnalyzer">View all items</a></small></p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="@Html.Raw(Model.PTMagicConfiguration.GeneralSettings.Monitor.RootUrl)assets/js/jquery.nicescroll.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function ($) {
|
||||
'use strict';
|
||||
|
||||
$("#dca-chart").on("show.bs.modal", function (e) {
|
||||
$(this).find(".modal-content").html('<i class="fa fa-circle-o-notch fa-spin fa-3x fa-fw"></i>');
|
||||
var link = $(e.relatedTarget);
|
||||
$(this).find(".modal-content").load(link.attr("href"), function () {
|
||||
$('.text-autocolor').autocolor(false);
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class DashboardTopModel : _Internal.BasePageModelSecureAJAX {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public DateTimeOffset DateTimeNow = Constants.confMinDate;
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
|
||||
// Convert local offset time to UTC
|
||||
TimeSpan offsetTimeSpan = TimeSpan.Parse(PTMagicConfiguration.GeneralSettings.Application.TimezoneOffset.Replace("+", ""));
|
||||
DateTimeNow = DateTimeOffset.UtcNow.ToOffset(offsetTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model DownloadFileModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class DownloadFileModel : _Internal.BasePageModelSecure {
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
InitializeDownload();
|
||||
}
|
||||
|
||||
private void InitializeDownload() {
|
||||
string fileName = GetStringParameter("f", "");
|
||||
if (System.IO.File.Exists(PTMagicBasePath + fileName)) {
|
||||
if (!System.IO.Directory.Exists(PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar)) {
|
||||
System.IO.Directory.CreateDirectory(PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
string sourcefilePath = PTMagicBasePath + fileName;
|
||||
string destinationFilePath = PTMagicMonitorBasePath + "wwwroot" + System.IO.Path.DirectorySeparatorChar + "assets" + System.IO.Path.DirectorySeparatorChar + "tmp" + System.IO.Path.DirectorySeparatorChar + fileName + ".zip";
|
||||
|
||||
ZIPHelper.CreateZipFile(new ArrayList() { sourcefilePath }, destinationFilePath);
|
||||
|
||||
Response.Redirect(PTMagicConfiguration.GeneralSettings.Monitor.RootUrl + "assets/tmp/" + fileName + ".zip");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
@page
|
||||
@model PresetFileModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<textarea id="preset-file">@Html.Raw(Model.FileContent)</textarea>
|
||||
<div id="editor"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var editor = ace.edit("editor");
|
||||
var txtArea = $('#preset-file');
|
||||
var jsbOpts = {
|
||||
indent_size: 2
|
||||
};
|
||||
editor.setTheme("ace/theme/ptmagic");
|
||||
editor.getSession().setMode("ace/mode/java");
|
||||
editor.getSession().setValue(txtArea.val());
|
||||
editor.getSession().on('change', function () {
|
||||
txtArea.val(editor.getSession().getValue());
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
using Core.ProfitTrailer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class PresetFileModel : _Internal.BasePageModelSecureAJAX {
|
||||
public string FileName = "";
|
||||
public string SettingName = "";
|
||||
public string FileContent = "";
|
||||
|
||||
public void OnGet() {
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
FileName = GetStringParameter("f", "");
|
||||
SettingName = GetStringParameter("gs", "");
|
||||
|
||||
if (!FileName.Equals("") && !SettingName.Equals("")) {
|
||||
List<string> presetFileLines = SettingsFiles.GetPresetFileLinesAsList(PTMagicBasePath, SettingName, FileName, PTMagicConfiguration);
|
||||
FileContent = SystemHelper.ConvertListToTokenString(presetFileLines, Environment.NewLine, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
@page
|
||||
@model ReturnToLoginModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
returntologin
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class ReturnToLoginModel : _Internal.BasePageModel {
|
||||
public void OnGet() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
@page
|
||||
@model SalesListModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title mt-0">Showing @Model.SellLog.Count sales for @Model.SalesTimeframe</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table id="sales-list" class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sold Time</th>
|
||||
<th>Market</th>
|
||||
<th class="text-right">Avg. Bought Price</th>
|
||||
<th class="text-right">DCA</th>
|
||||
<th class="text-right">Sold Price</th>
|
||||
<th class="text-right">Sold Amount</th>
|
||||
<th class="text-right">Bought Cost</th>
|
||||
<th class="text-right">Sold Value</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainMarket</th>
|
||||
<th class="text-right">Profit @Model.Summary.MainFiatCurrency</th>
|
||||
<th class="text-right">Profit %</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.SellLogData sellLogEntry in Model.SellLog) {
|
||||
double profitFiat = Math.Round(sellLogEntry.Profit * Model.Summary.MainMarketPrice, 2);
|
||||
<tr>
|
||||
<td>@sellLogEntry.SoldDate.ToShortDateString() @sellLogEntry.SoldDate.ToLongTimeString()</td>
|
||||
<td><a href="@Core.Helper.SystemHelper.GetMarketLink(Model.PTMagicConfiguration.GeneralSettings.Monitor.LinkPlatform,Model.PTMagicConfiguration.GeneralSettings.Application.Exchange, sellLogEntry.Market, Model.Summary.MainMarket)" target="_blank">@sellLogEntry.Market</a></td>
|
||||
<td class="text-right">@sellLogEntry.AverageBuyPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">
|
||||
@if (sellLogEntry.BoughtTimes > 0) {
|
||||
@sellLogEntry.BoughtTimes;
|
||||
}
|
||||
</td>
|
||||
<td class="text-right">@sellLogEntry.SoldPrice.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@sellLogEntry.SoldAmount.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@sellLogEntry.TotalCost.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right">@sellLogEntry.SoldValue.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@sellLogEntry.Profit.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@profitFiat.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))</td>
|
||||
<td class="text-right text-autocolor">@sellLogEntry.ProfitPercent.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"))%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
//Buttons examples
|
||||
var table = $('#sales-list').DataTable({
|
||||
lengthChange: false,
|
||||
searching: false,
|
||||
paging: false,
|
||||
info: false,
|
||||
ordering: false,
|
||||
buttons: ['copy', 'excel', 'pdf']
|
||||
});
|
||||
|
||||
table.buttons().container()
|
||||
.appendTo('#sales-list_wrapper .col-md-6:eq(0)');
|
||||
|
||||
$('.btn-trend-relation').click(function () {
|
||||
var relation = $(this).data('trend-relation');
|
||||
|
||||
$('.btn-trend-relation').addClass('btn-custom');
|
||||
$(this).removeClass('btn-custom');
|
||||
|
||||
if (relation == 'absolute') {
|
||||
$('#trends-absolute').removeClass('hidden');
|
||||
$('#trends-relative').addClass('hidden');
|
||||
|
||||
} else {
|
||||
$('#trends-absolute').addClass('hidden');
|
||||
$('#trends-relative').removeClass('hidden');
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SalesListModel : _Internal.BasePageModelSecure {
|
||||
public ProfitTrailerData PTData = null;
|
||||
private string salesDateString = "";
|
||||
private string salesMonthString = "";
|
||||
|
||||
public string SalesTimeframe = "";
|
||||
public DateTime SalesDate = Constants.confMinDate;
|
||||
public List<SellLogData> SellLog = new List<SellLogData>();
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
salesDateString = GetStringParameter("d", "");
|
||||
salesMonthString = GetStringParameter("m", "");
|
||||
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
|
||||
if (!salesDateString.Equals("")) {
|
||||
SalesDate = SystemHelper.TextToDateTime(salesDateString, Constants.confMinDate);
|
||||
if (SalesDate != Constants.confMinDate) {
|
||||
SalesTimeframe = SalesDate.ToShortDateString();
|
||||
SellLog = PTData.SellLog.FindAll(sl => sl.SoldDate.Date == SalesDate.Date).OrderByDescending(sl => sl.SoldDate).ToList();
|
||||
}
|
||||
} else if (!salesMonthString.Equals("")) {
|
||||
SalesDate = SystemHelper.TextToDateTime(salesMonthString + "-01", Constants.confMinDate);
|
||||
if (SalesDate != Constants.confMinDate) {
|
||||
SalesTimeframe = SalesDate.ToString("MMMM yyyy", new System.Globalization.CultureInfo("en-US"));
|
||||
SellLog = PTData.SellLog.FindAll(sl => sl.SoldDate.Date.Month == SalesDate.Month && sl.SoldDate.Date.Year == SalesDate.Year).OrderByDescending(sl => sl.SoldDate).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
@page
|
||||
@model SettingsGlobalSettingsModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<div id="GlobalSetting_@Model.SettingName">
|
||||
<a name="GlobalSetting_@Model.SettingName"></a>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
@if (!Model.GlobalSetting.SettingName.Equals("")) {
|
||||
<h4 class="m-t-0 m-b-20 header-title">
|
||||
Global Setting "<span class="text-ptmagic">@Model.GlobalSetting.SettingName</span>"
|
||||
@if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-sort btn-xs pull-right btn-move-GS btn-move-GS@(Model.SettingName)" data-datadirection="down" data-datatarget="GlobalSetting_@Model.SettingName" data-toggle="tooltip" data-placement="top" title="Move down"><i class="fa fa-chevron-down"></i></button>
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-sort btn-xs pull-right btn-move-GS btn-move-GS@(Model.SettingName) m-r-5" data-datadirection="up" data-datatarget="GlobalSetting_@Model.SettingName" data-toggle="tooltip" data-placement="top" title="Move up"><i class="fa fa-chevron-up"></i></button>
|
||||
}
|
||||
</h4>
|
||||
} else {
|
||||
<h4 class="m-t-0 header-title">New Global Setting</h4>
|
||||
}
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Setting Name (Unique) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="UNIQUE name of your setting"></i></label>
|
||||
<div class="col-md-8">
|
||||
@if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_GlobalSetting_@(Model.SettingName)|SettingName" value="@Core.Helper.SystemHelper.StripBadCode(Model.GlobalSetting.SettingName, Core.Main.Constants.WhiteListNames)">
|
||||
<span class="help-block"><small>Allowed characters: Letters, Numbers and '_'</small></span>
|
||||
} else {
|
||||
<input type="hidden" class="form-control" name="MarketAnalyzer_GlobalSetting_@(Model.SettingName)|SettingName" value="@Core.Helper.SystemHelper.StripBadCode(Model.GlobalSetting.SettingName, Core.Main.Constants.WhiteListNames)">
|
||||
@Model.GlobalSetting.SettingName
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Model.GlobalSetting.SettingName.StartsWith("Default", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if triggers will be connected by AND or OR"></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="MarketAnalyzer_GlobalSetting_@(Model.SettingName)|TriggerConnection" class="form-control">
|
||||
<option selected="@(Model.GlobalSetting.TriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
|
||||
<option selected="@(Model.GlobalSetting.TriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Triggers (@Model.GlobalSetting.Triggers.Count)</h5>
|
||||
<div id="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_Triggers"></div>
|
||||
|
||||
<button id="btn-new-standardtrigger-GS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_Triggers">
|
||||
Add Trigger
|
||||
</button>
|
||||
}
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Pairs Properties (@Model.GlobalSetting.PairsProperties.Count)</h5>
|
||||
<div id="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_PairsProperties"></div>
|
||||
|
||||
<button id="btn-new-pairs-property-GS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_PairsProperties">
|
||||
Add Property
|
||||
</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">DCA Properties (@Model.GlobalSetting.DCAProperties.Count)</h5>
|
||||
<div id="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_DCAProperties"></div>
|
||||
<button id="btn-new-dca-property-GS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_DCAProperties">
|
||||
Add Property
|
||||
</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Indicators Properties (@Model.GlobalSetting.IndicatorsProperties.Count)</h5>
|
||||
<div id="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_IndicatorsProperties"></div>
|
||||
|
||||
<button id="btn-new-indicators-property-GS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_GlobalSetting_@(Model.SettingName)_IndicatorsProperties">
|
||||
Add Property
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger m-t-20 btn-block text-uppercase waves-effect waves-light btn-remove-GS@(Model.SettingName)" data-datatarget="GlobalSetting_@Model.SettingName">
|
||||
Remove Global Setting "@Model.GlobalSetting.SettingName"
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
function BindRemoveParentRow@(Model.SettingName)Event() {
|
||||
$('.btn-remove-parentrow-GlobalSetting-@(Model.SettingName)').click(function () {
|
||||
$(this).closest('.row').animate({ height: 0 }, 300, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
$('#MarketAnalyzer_GlobalSetting_@(Model.SettingName)_Triggers').html([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.Trigger t in Model.GlobalSetting.Triggers) {
|
||||
string minChangeValue = t.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
if (t.MinChange == Core.Main.Constants.MinTrendChange) {
|
||||
minChangeValue = "";
|
||||
}
|
||||
|
||||
string maxChangeValue = t.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
if (t.MaxChange == Core.Main.Constants.MaxTrendChange) {
|
||||
maxChangeValue = "";
|
||||
}
|
||||
<text>
|
||||
{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', trendName: '@Core.Helper.SystemHelper.StripBadCode(t.MarketTrendName, Core.Main.Constants.WhiteListNames)', minChange: '@minChangeValue', maxChange: '@maxChangeValue', marketTrends: '@Html.Raw(Model.GetMarketTrendSelection(t))' },
|
||||
</text>
|
||||
}
|
||||
].map(GSStandardTriggerTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_GlobalSetting_@(Model.SettingName)_PairsProperties').html([
|
||||
@foreach (KeyValuePair<string, object> property in Model.GlobalSetting.PairsProperties) {
|
||||
string propertyKeySimple = property.Key.Replace("_OFFSETPERCENT", "").Replace("_OFFSET", "");
|
||||
<text>
|
||||
{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', propertyType: 'Pairs', propertyKeySimple: '@propertyKeySimple', value: '@Core.Helper.SystemHelper.PropertyToString(property.Value)', valueModes: '@Html.Raw(Model.GetValueModes(property.Key))' },
|
||||
</text>
|
||||
}
|
||||
].map(PropertyTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_GlobalSetting_@(Model.SettingName)_DCAProperties').html([
|
||||
@foreach (KeyValuePair<string, object> property in Model.GlobalSetting.DCAProperties) {
|
||||
string propertyKeySimple = property.Key.Replace("_OFFSETPERCENT", "").Replace("_OFFSET", "");
|
||||
<text>
|
||||
{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', propertyType: 'DCA', propertyKeySimple: '@propertyKeySimple', value: '@Core.Helper.SystemHelper.PropertyToString(property.Value)', valueModes: '@Html.Raw(Model.GetValueModes(property.Key))' },
|
||||
</text>
|
||||
}
|
||||
].map(PropertyTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_GlobalSetting_@(Model.SettingName)_IndicatorsProperties').html([
|
||||
@foreach (KeyValuePair<string, object> property in Model.GlobalSetting.IndicatorsProperties) {
|
||||
string propertyKeySimple = property.Key.Replace("_OFFSETPERCENT", "").Replace("_OFFSET", "");
|
||||
<text>
|
||||
{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', propertyType: 'Indicators', propertyKeySimple: '@propertyKeySimple', value: '@Core.Helper.SystemHelper.PropertyToString(property.Value)', valueModes: '@Html.Raw(Model.GetValueModes(property.Key))' },
|
||||
</text>
|
||||
}
|
||||
].map(PropertyTemplate).join(''));
|
||||
|
||||
|
||||
$('#btn-new-standardtrigger-GS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', trendName: '', minChange: '', maxChange: '', marketTrends: '@Html.Raw(Model.GetMarketTrendSelection(null))' }].map(GSStandardTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRow@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-pairs-property-GS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', propertyType: 'Pairs', propertyKeySimple: '', value: '', valueModes: '@Html.Raw(Model.GetValueModes(""))' }].map(PropertyTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRow@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-dca-property-GS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', propertyType: 'DCA', propertyKeySimple: '', value: '', valueModes: '@Html.Raw(Model.GetValueModes(""))' }].map(PropertyTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRow@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-indicators-property-GS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'GlobalSetting', settingName: '@(Model.SettingName)', propertyType: 'Indicators', propertyKeySimple: '', value: '', valueModes: '@Html.Raw(Model.GetValueModes(""))' }].map(PropertyTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRow@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-remove-GS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
|
||||
$('#' + dataTarget).animate({ height: 0 }, 300, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-move-GS@(Model.SettingName)').click(function () {
|
||||
checkedGSButtons = false;
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataDirection = $(this).data('datadirection');
|
||||
|
||||
var currentElement = $('#' + dataTarget).closest('.settings-globalsetting');
|
||||
|
||||
var prevElement = $(currentElement).prev('.settings-globalsetting');
|
||||
var nextElement = $(currentElement).next('.settings-globalsetting');
|
||||
|
||||
if (dataDirection === 'down') {
|
||||
$(currentElement).insertAfter($(nextElement));
|
||||
$('html, body').scrollTop($(currentElement).offset().top - 100);
|
||||
} else if (dataDirection === 'up') {
|
||||
$(currentElement).insertBefore($(prevElement));
|
||||
$('html, body').scrollTop($(currentElement).offset().top - 100);
|
||||
}
|
||||
checkGSMoveButtons();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
BindRemoveParentRow@(Model.SettingName)Event();
|
||||
});
|
||||
</script>
|
||||
</div>
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SettingsGlobalSettingsModel : _Internal.BasePageModelSecure {
|
||||
public GlobalSetting GlobalSetting = null;
|
||||
public string SettingName = "";
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
SettingName = this.GetStringParameter("gs", "");
|
||||
if (!SettingName.Equals("")) {
|
||||
GlobalSetting = PTMagicConfiguration.AnalyzerSettings.GlobalSettings.Find(gs => SystemHelper.StripBadCode(gs.SettingName, Constants.WhiteListNames).Equals(SettingName));
|
||||
} else {
|
||||
GlobalSetting = new GlobalSetting();
|
||||
GlobalSetting.SettingName = "New Setting";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMarketTrendSelection(Trigger t) {
|
||||
string result = "";
|
||||
|
||||
foreach (MarketTrend mt in PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends) {
|
||||
string selected = "";
|
||||
if (t != null) {
|
||||
if (t.MarketTrendName.Equals(mt.Name, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
}
|
||||
|
||||
result += "<option" + selected + " value=\"" + SystemHelper.StripBadCode(mt.Name, Constants.WhiteListNames) + "\">" + mt.Name + "</option>";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetValueModes(string propertyKey) {
|
||||
string result = "";
|
||||
|
||||
string selected = "";
|
||||
if (propertyKey.IndexOf("_OFFSET", StringComparison.InvariantCultureIgnoreCase) == -1) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
result += "<option" + selected + " value=\"\">Flat value</option>";
|
||||
|
||||
if (propertyKey.EndsWith("_OFFSET", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
result += "<option" + selected + " value=\"_OFFSET\">Offset by flat value</option>";
|
||||
|
||||
if (propertyKey.EndsWith("_OFFSETPERCENT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
result += "<option" + selected + " value=\"_OFFSETPERCENT\">Offset by percent</option>";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
@page
|
||||
@model SettingsMarketTrendsModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<div id="MarketTrend_@Model.MarketTrendName">
|
||||
<a name="MarketTrend_@Model.MarketTrendName"></a>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
@if (!Model.MarketTrend.Name.Equals("")) {
|
||||
<h4 class="m-t-0 m-b-20 header-title">
|
||||
Market Trend "<span class="text-ptmagic">@Model.MarketTrend.Name</span>"
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-sort btn-xs pull-right btn-move-MT btn-move-MT@(Model.MarketTrendName)" data-datadirection="down" data-datatarget="MarketTrend_@Model.MarketTrendName" data-toggle="tooltip" data-placement="top" title="Move down"><i class="fa fa-chevron-down"></i></button>
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-sort btn-xs pull-right btn-move-MT btn-move-MT@(Model.MarketTrendName) m-r-5" data-datadirection="up" data-datatarget="MarketTrend_@Model.MarketTrendName" data-toggle="tooltip" data-placement="top" title="Move up"><i class="fa fa-chevron-up"></i></button>
|
||||
</h4>
|
||||
} else {
|
||||
<h4 class="m-t-0 m-b-20 header-title">New Market Trend</h4>
|
||||
}
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Name (Unique) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="UNIQUE market trend name (to be referenced by your triggers below)"></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|Name" value="@Core.Helper.SystemHelper.StripBadCode(Model.MarketTrend.Name, Core.Main.Constants.WhiteListNames)">
|
||||
<span class="help-block"><small>Allowed characters: Letters, Numbers and '_'</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Platform <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Platform to grab prices from"></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|Platform" class="form-control">
|
||||
<option selected="@(Model.MarketTrend.Platform.Equals("CoinMarketCap", StringComparison.InvariantCultureIgnoreCase))">CoinMarketCap</option>
|
||||
<option selected="@(Model.MarketTrend.Platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase))">Exchange</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Max Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Number of markets/pairs to analyze sorted by 24h volume."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|MaxMarkets" value="@Model.MarketTrend.MaxMarkets.ToString()">
|
||||
<span class="help-block"><small>0 = All available markets</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Trend Minutes <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Number of minutes to build a trend (1440 = 24h, 720 = 12h, 60 = 1h)."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|TrendMinutes" value="@Model.MarketTrend.TrendMinutes.ToString()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Trend Currency <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Trend Currency to build the trend against. If set to 'Fiat', the trend will take the USD value of your main currency into account to build the trend."></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|TrendCurrency" class="form-control">
|
||||
<option selected="@(Model.MarketTrend.TrendCurrency.Equals("Market", StringComparison.InvariantCultureIgnoreCase))">Market</option>
|
||||
<option selected="@(Model.MarketTrend.TrendCurrency.Equals("Fiat", StringComparison.InvariantCultureIgnoreCase))">Fiat</option>
|
||||
</select>
|
||||
<span class="help-block"><small>Market = Trend of each coin against your main market (BTC, ETH) / Fiat = Trend of each coin against USD</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Ignored Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Comma separated list of markets you want to be ignored in this market trend."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|IgnoredMarkets" value="@Model.MarketTrend.IgnoredMarkets">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Allowed Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Comma separated list of markets you want to be allowed in this market trend."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|AllowedMarkets" value="@Model.MarketTrend.AllowedMarkets">
|
||||
<span class="help-block"><small>Leave empty to allow all</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Display Graph <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Displays or hides the graph for this trend on the dashboard of your PT Magic monitor."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|DisplayGraph" checked="@(Model.MarketTrend.DisplayGraph)" data-plugin="switchery" data-switchery="false" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Exclude Main Currency <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Excludes the main currency (for example BTC) from market trend analysis."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="MarketAnalyzer_MarketTrend_@(Model.MarketTrendName)|ExcludeMainCurrency" checked="@(Model.MarketTrend.ExcludeMainCurrency)" data-plugin="switchery" data-switchery="false" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-danger btn-block text-uppercase waves-effect waves-light btn-remove-MT@(Model.MarketTrendName)" data-datatarget="MarketTrend_@Model.MarketTrendName">
|
||||
Remove Market Trend "@Model.MarketTrend.Name"
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
|
||||
$('.btn-remove-MT@(Model.MarketTrendName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
|
||||
$('#' + dataTarget).animate({ height: 0 }, 300, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-move-MT@(Model.MarketTrendName)').click(function () {
|
||||
checkedMTButtons = false;
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataDirection = $(this).data('datadirection');
|
||||
|
||||
var currentElement = $('#' + dataTarget).closest('.settings-markettrend');
|
||||
|
||||
var prevElement = $(currentElement).prev('.settings-markettrend');
|
||||
var nextElement = $(currentElement).next('.settings-markettrend');
|
||||
|
||||
if (dataDirection === 'down') {
|
||||
$(currentElement).insertAfter($(nextElement));
|
||||
$('html, body').scrollTop($(currentElement).offset().top - 100);
|
||||
} else if (dataDirection === 'up') {
|
||||
$(currentElement).insertBefore($(prevElement));
|
||||
$('html, body').scrollTop($(currentElement).offset().top - 100);
|
||||
}
|
||||
checkMTMoveButtons();
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SettingsMarketTrendsModel : _Internal.BasePageModelSecure {
|
||||
public MarketTrend MarketTrend = null;
|
||||
public string MarketTrendName = "";
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
MarketTrendName = this.GetStringParameter("mt", "");
|
||||
if (!MarketTrendName.Equals("")) {
|
||||
MarketTrend = PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.Find(m => SystemHelper.StripBadCode(m.Name, Constants.WhiteListNames).Equals(MarketTrendName));
|
||||
} else {
|
||||
MarketTrend = new MarketTrend();
|
||||
MarketTrend.Name = "New Market Trend";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
@page
|
||||
@model SettingsSingleMarketSettingsModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<div id="SingleMarketSetting_@Model.SettingName">
|
||||
<a name="SingleMarketSetting_@Model.SettingName"></a>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card-box">
|
||||
@if (!Model.SingleMarketSetting.SettingName.Equals("")) {
|
||||
<h4 class="m-t-0 m-b-20 header-title">
|
||||
Single Market Setting "<span class="text-ptmagic">@Model.SingleMarketSetting.SettingName</span>"
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-sort btn-xs pull-right btn-move-SMS btn-move-SMS@(Model.SettingName)" data-datadirection="down" data-datatarget="SingleMarketSetting_@Model.SettingName" data-toggle="tooltip" data-placement="top" title="Move down"><i class="fa fa-chevron-down"></i></button>
|
||||
<button class="btn btn-ptmagic text-uppercase waves-effect waves-light btn-sort btn-xs pull-right btn-move-SMS btn-move-SMS@(Model.SettingName) m-r-5" data-datadirection="up" data-datatarget="SingleMarketSetting_@Model.SettingName" data-toggle="tooltip" data-placement="top" title="Move up"><i class="fa fa-chevron-up"></i></button>
|
||||
</h4>
|
||||
} else {
|
||||
<h4 class="m-t-0 header-title">New Single Market Setting</h4>
|
||||
}
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Setting Name (Unique) <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="UNIQUE name of your setting"></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|SettingName" value="@Core.Helper.SystemHelper.StripBadCode(Model.SingleMarketSetting.SettingName, Core.Main.Constants.WhiteListNames)">
|
||||
<span class="help-block"><small>Allowed characters: Letters, Numbers and '_'</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if triggers will be connected by AND or OR"></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|TriggerConnection" class="form-control">
|
||||
<option selected="@(Model.SingleMarketSetting.TriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
|
||||
<option selected="@(Model.SingleMarketSetting.TriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Off Trigger Connection <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Define if off triggers will be connected by AND or OR"></i></label>
|
||||
<div class="col-md-8">
|
||||
<select name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|OffTriggerConnection" class="form-control">
|
||||
<option selected="@(Model.SingleMarketSetting.OffTriggerConnection.Equals("AND", StringComparison.InvariantCultureIgnoreCase))">AND</option>
|
||||
<option selected="@(Model.SingleMarketSetting.OffTriggerConnection.Equals("OR", StringComparison.InvariantCultureIgnoreCase))">OR</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Ignored Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Comma separated list of markets you want to be ignored in this single market setting."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|IgnoredMarkets" value="@Model.SingleMarketSetting.IgnoredMarkets">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Allowed Markets <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Comma separated list of markets that are allowed for this single market setting to get applied to."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|AllowedMarkets" value="@Model.SingleMarketSetting.AllowedMarkets">
|
||||
<span class="help-block"><small>Leave empty to allow all</small></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-md-4 col-form-label">Stop Process When Triggered <i class="fa fa-info-circle text-muted" data-toggle="tooltip" data-placement="top" title="Stops looking for other single market settings when this setting gets triggered for a market."></i></label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" name="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)|StopProcessWhenTriggered" checked="@(Model.SingleMarketSetting.StopProcessWhenTriggered)" data-plugin="switchery" data-switchery="false" data-color="#81c868" data-size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Triggers (@Model.SingleMarketSetting.Triggers.Count)</h5>
|
||||
<div id="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers"></div>
|
||||
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-4">
|
||||
<button id="btn-new-standardtrigger-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers">
|
||||
New Standard Trigger
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button id="btn-new-24hvolumetrigger-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers">
|
||||
New 24h Volume Trigger
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button id="btn-new-coinagetrigger-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers">
|
||||
New Coin Age Trigger
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Off Triggers (@Model.SingleMarketSetting.OffTriggers.Count)</h5>
|
||||
<div id="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers"></div>
|
||||
|
||||
<div class="row m-b-20">
|
||||
<div class="col-md-4">
|
||||
<button id="btn-new-standardofftrigger-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers">
|
||||
New Standard Off Trigger
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button id="btn-new-24hvolumeofftrigger-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers">
|
||||
New 24h Volume Off Trigger
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button id="btn-new-hoursactiveofftrigger-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers">
|
||||
New Timed Off Trigger
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Pairs Properties (@Model.SingleMarketSetting.PairsProperties.Count)</h5>
|
||||
<div id="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_PairsProperties"></div>
|
||||
|
||||
<button id="btn-new-pairs-property-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_PairsProperties">
|
||||
Add Property
|
||||
</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">DCA Properties (@Model.SingleMarketSetting.DCAProperties.Count)</h5>
|
||||
<div id="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_DCAProperties"></div>
|
||||
|
||||
<button id="btn-new-dca-property-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_DCAProperties">
|
||||
Add Property
|
||||
</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5 class="header-title m-b-20">Indicators Properties (@Model.SingleMarketSetting.IndicatorsProperties.Count)</h5>
|
||||
<div id="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_IndicatorsProperties"></div>
|
||||
|
||||
<button id="btn-new-indicators-property-SMS@(Model.SettingName)" class="btn btn-ptmagic btn-custom btn-block text-uppercase waves-effect waves-light m-b-20" data-datatarget="MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_IndicatorsProperties">
|
||||
Add Property
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger m-t-20 btn-block text-uppercase waves-effect waves-light btn-remove-SMS@(Model.SettingName)" data-datatarget="SingleMarketSetting_@Model.SettingName">
|
||||
Remove Single Market Setting "@Model.SingleMarketSetting.SettingName"
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function BindRemoveParentRowSMS@(Model.SettingName)Event() {
|
||||
$('.btn-remove-parentrow-SingleMarketSetting-@(Model.SettingName)').click(function () {
|
||||
$(this).closest('.row').animate({ height: 0 }, 300, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers').html([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.Trigger t in Model.SingleMarketSetting.Triggers.FindAll(t => t.AgeDaysLowerThan == 0 && t.Min24hVolume == 0 && t.Max24hVolume == Core.Main.Constants.Max24hVolume)) {
|
||||
string minChangeValue = t.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
if (t.MinChange == Core.Main.Constants.MinTrendChange) {
|
||||
minChangeValue = "";
|
||||
}
|
||||
|
||||
string maxChangeValue = t.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
if (t.MaxChange == Core.Main.Constants.MaxTrendChange) {
|
||||
maxChangeValue = "";
|
||||
}
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', trendName: '@Core.Helper.SystemHelper.StripBadCode(t.MarketTrendName, Core.Main.Constants.WhiteListNames)', triggerPrefix: '', minChange: '@minChangeValue', maxChange: '@maxChangeValue', marketTrends: '@Html.Raw(Model.GetMarketTrendSelection(t))', marketTrendRelations: '@Html.Raw(Model.GetMarketTrendRelationSelection(t))' },
|
||||
</text>
|
||||
}
|
||||
].map(SMSStandardTriggerTemplate).join(''));
|
||||
|
||||
@{ int ageDaysIndex = 0;}
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers').append([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.Trigger t in Model.SingleMarketSetting.Triggers.FindAll(t => t.AgeDaysLowerThan > 0)) {
|
||||
ageDaysIndex++;
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@ageDaysIndex', coinAgeDays: '@t.AgeDaysLowerThan.ToString()' },
|
||||
</text>
|
||||
}
|
||||
].map(CoinAgeTriggerTemplate).join(''));
|
||||
|
||||
@{ int volume24hIndex = 0;}
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_Triggers').append([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.Trigger t in Model.SingleMarketSetting.Triggers.FindAll(t => t.Min24hVolume > 0 || t.Max24hVolume < Core.Main.Constants.Max24hVolume)) {
|
||||
string min24hVolumeValue = t.Min24hVolume.ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
if (t.Min24hVolume == 0) {
|
||||
min24hVolumeValue = "";
|
||||
}
|
||||
|
||||
string max24hVolumeValue = t.Max24hVolume.ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
if (t.Max24hVolume == Core.Main.Constants.Max24hVolume) {
|
||||
max24hVolumeValue = "";
|
||||
}
|
||||
volume24hIndex++;
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@volume24hIndex', triggerPrefix: '', mainMarket: '@Model.Summary.MainMarket', min24hVolume: '@min24hVolumeValue', max24hVolume: '@max24hVolumeValue' },
|
||||
</text>
|
||||
}
|
||||
].map(SMS24hVolumeTriggerTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers').html([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.OffTrigger ot in Model.SingleMarketSetting.OffTriggers.FindAll(ot => ot.HoursSinceTriggered == 0 && ot.Min24hVolume == 0 && ot.Max24hVolume == Core.Main.Constants.Max24hVolume)) {
|
||||
string minChangeValue = ot.MinChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
if (ot.MinChange == Core.Main.Constants.MinTrendChange) {
|
||||
minChangeValue = "";
|
||||
}
|
||||
|
||||
string maxChangeValue = ot.MaxChange.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
if (ot.MaxChange == Core.Main.Constants.MaxTrendChange) {
|
||||
maxChangeValue = "";
|
||||
}
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', trendName: '@Core.Helper.SystemHelper.StripBadCode(ot.MarketTrendName, Core.Main.Constants.WhiteListNames)', triggerPrefix: 'Off', minChange: '@minChangeValue', maxChange: '@maxChangeValue', marketTrends: '@Html.Raw(Model.GetOffTriggerMarketTrendSelection(ot))', marketTrendRelations: '@Html.Raw(Model.GetOffTriggerMarketTrendRelationSelection(ot))' },
|
||||
</text>
|
||||
}
|
||||
].map(SMSStandardTriggerTemplate).join(''));
|
||||
|
||||
@{ int volume24hOffIndex = 0;}
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers').append([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.OffTrigger ot in Model.SingleMarketSetting.OffTriggers.FindAll(ot => ot.Min24hVolume > 0 || ot.Max24hVolume < Core.Main.Constants.Max24hVolume)) {
|
||||
string min24hVolumeValue = ot.Min24hVolume.ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
if (ot.Min24hVolume == 0) {
|
||||
min24hVolumeValue = "";
|
||||
}
|
||||
|
||||
string max24hVolumeValue = ot.Max24hVolume.ToString(new System.Globalization.CultureInfo("en-US"));
|
||||
if (ot.Max24hVolume == Core.Main.Constants.Max24hVolume) {
|
||||
max24hVolumeValue = "";
|
||||
}
|
||||
volume24hOffIndex++;
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@volume24hOffIndex', triggerPrefix: 'Off', mainMarket: '@Model.Summary.MainMarket', min24hVolume: '@min24hVolumeValue', max24hVolume: '@max24hVolumeValue' },
|
||||
</text>
|
||||
}
|
||||
].map(SMS24hVolumeTriggerTemplate).join(''));
|
||||
|
||||
@{ int activeHoursIndex = 0;}
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_OffTriggers').append([
|
||||
@foreach (Core.Main.DataObjects.PTMagicData.OffTrigger ot in Model.SingleMarketSetting.OffTriggers.FindAll(t => t.HoursSinceTriggered > 0)) {
|
||||
activeHoursIndex++;
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@activeHoursIndex', hoursSinceTriggered: '@ot.HoursSinceTriggered.ToString()' },
|
||||
</text>
|
||||
}
|
||||
].map(SMSHoursActiveTriggerTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_PairsProperties').html([
|
||||
@foreach (KeyValuePair<string, object> property in Model.SingleMarketSetting.PairsProperties) {
|
||||
string propertyKeySimple = property.Key.Replace("_OFFSETPERCENT", "").Replace("_OFFSET", "");
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', propertyType: 'Pairs', propertyKeySimple: '@propertyKeySimple', value: '@Core.Helper.SystemHelper.PropertyToString(property.Value)', valueModes: '@Html.Raw(Model.GetValueModes(property.Key))' },
|
||||
</text>
|
||||
}
|
||||
].map(PropertyTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_DCAProperties').html([
|
||||
@foreach (KeyValuePair<string, object> property in Model.SingleMarketSetting.DCAProperties) {
|
||||
string propertyKeySimple = property.Key.Replace("_OFFSETPERCENT", "").Replace("_OFFSET", "");
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', propertyType: 'DCA', propertyKeySimple: '@propertyKeySimple', value: '@Core.Helper.SystemHelper.PropertyToString(property.Value)', valueModes: '@Html.Raw(Model.GetValueModes(property.Key))' },
|
||||
</text>
|
||||
}
|
||||
].map(PropertyTemplate).join(''));
|
||||
|
||||
$('#MarketAnalyzer_SingleMarketSetting_@(Model.SettingName)_IndicatorsProperties').html([
|
||||
@foreach (KeyValuePair<string, object> property in Model.SingleMarketSetting.IndicatorsProperties) {
|
||||
string propertyKeySimple = property.Key.Replace("_OFFSETPERCENT", "").Replace("_OFFSET", "");
|
||||
<text>
|
||||
{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', propertyType: 'Indicators', propertyKeySimple: '@propertyKeySimple', value: '@Core.Helper.SystemHelper.PropertyToString(property.Value)', valueModes: '@Html.Raw(Model.GetValueModes(property.Key))' },
|
||||
</text>
|
||||
}
|
||||
].map(PropertyTemplate).join(''));
|
||||
|
||||
$('#btn-new-standardtrigger-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', trendName: '', triggerPrefix: '', minChange: '', maxChange: '', marketTrends: '@Html.Raw(Model.GetMarketTrendSelection(null))', marketTrendRelations: '@Html.Raw(Model.GetMarketTrendRelationSelection(null))' }].map(SMSStandardTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-24hvolumetrigger-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@volume24hIndex', triggerPrefix: '', mainMarket: '@Model.Summary.MainMarket', min24hVolume: '', max24hVolume: '' }].map(SMS24hVolumeTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-coinagetrigger-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@ageDaysIndex', coinAgeDays: '' }].map(CoinAgeTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-standardofftrigger-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', trendName: '', triggerPrefix: 'Off', minChange: '', maxChange: '', marketTrends: '@Html.Raw(Model.GetOffTriggerMarketTrendSelection(null))', marketTrendRelations: '@Html.Raw(Model.GetOffTriggerMarketTrendRelationSelection(null))' }].map(SMSStandardTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-24hvolumeofftrigger-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@volume24hIndex', triggerPrefix: 'Off', mainMarket: '@Model.Summary.MainMarket', min24hVolume: '', max24hVolume: '' }].map(SMS24hVolumeTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-hoursactiveofftrigger-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', triggerIndex: '@volume24hIndex', triggerPrefix: 'Off', hoursSinceTriggered: '' }].map(SMSHoursActiveTriggerTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-pairs-property-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', propertyType: 'Pairs', propertyKeySimple: '', value: '', valueModes: '@Html.Raw(Model.GetValueModes(""))' }].map(PropertyTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-dca-property-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', propertyType: 'DCA', propertyKeySimple: '', value: '', valueModes: '@Html.Raw(Model.GetValueModes(""))' }].map(PropertyTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#btn-new-indicators-property-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
$('#' + dataTarget).append([{ settingType: 'SingleMarketSetting', settingName: '@(Model.SettingName)', propertyType: 'Indicators', propertyKeySimple: '', value: '', valueModes: '@Html.Raw(Model.GetValueModes(""))' }].map(PropertyTemplate).join(''));
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-remove-SMS@(Model.SettingName)').click(function () {
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
|
||||
$('#' + dataTarget).animate({ height: 0 }, 300, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.btn-move-SMS@(Model.SettingName)').click(function () {
|
||||
checkedSMSButtons = false;
|
||||
var dataTarget = $(this).data('datatarget');
|
||||
var dataDirection = $(this).data('datadirection');
|
||||
|
||||
var currentElement = $('#' + dataTarget).closest('.settings-singlemarketsetting');
|
||||
|
||||
var prevElement = $(currentElement).prev('.settings-singlemarketsetting');
|
||||
var nextElement = $(currentElement).next('.settings-singlemarketsetting');
|
||||
|
||||
if (dataDirection === 'down') {
|
||||
$(currentElement).insertAfter($(nextElement));
|
||||
$('html, body').scrollTop($(currentElement).offset().top - 100);
|
||||
} else if (dataDirection === 'up') {
|
||||
$(currentElement).insertBefore($(prevElement));
|
||||
$('html, body').scrollTop($(currentElement).offset().top - 100);
|
||||
}
|
||||
checkSMSMoveButtons();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).ready(function () { BindRemoveParentRowSMS@(Model.SettingName)Event(); });
|
||||
});
|
||||
</script>
|
||||
</div>
|
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SettingsSingleMarketSettingsModel : _Internal.BasePageModelSecure {
|
||||
public SingleMarketSetting SingleMarketSetting = null;
|
||||
public string SettingName = "";
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
SettingName = this.GetStringParameter("gs", "");
|
||||
if (!SettingName.Equals("")) {
|
||||
SingleMarketSetting = PTMagicConfiguration.AnalyzerSettings.SingleMarketSettings.Find(sms => SystemHelper.StripBadCode(sms.SettingName, Constants.WhiteListNames).Equals(SettingName));
|
||||
} else {
|
||||
SingleMarketSetting = new SingleMarketSetting();
|
||||
SingleMarketSetting.SettingName = "New Setting";
|
||||
}
|
||||
}
|
||||
|
||||
public string GetMarketTrendSelection(Trigger t) {
|
||||
string result = "";
|
||||
|
||||
foreach (MarketTrend mt in PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.FindAll(m => m.Platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase))) {
|
||||
string selected = "";
|
||||
if (t != null) {
|
||||
if (t.MarketTrendName.Equals(mt.Name, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
}
|
||||
|
||||
result += "<option" + selected + " value=\"" + SystemHelper.StripBadCode(mt.Name, Constants.WhiteListNames) + "\">" + mt.Name + "</option>";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetOffTriggerMarketTrendSelection(OffTrigger ot) {
|
||||
string result = "";
|
||||
|
||||
foreach (MarketTrend mt in PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.MarketTrends.FindAll(m => m.Platform.Equals("Exchange", StringComparison.InvariantCultureIgnoreCase))) {
|
||||
string selected = "";
|
||||
if (ot != null) {
|
||||
if (ot.MarketTrendName.Equals(mt.Name, StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
}
|
||||
|
||||
result += "<option" + selected + " value=\"" + SystemHelper.StripBadCode(mt.Name, Constants.WhiteListNames) + "\">" + mt.Name + "</option>";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetMarketTrendRelationSelection(Trigger t) {
|
||||
string result = "";
|
||||
|
||||
string selected = "";
|
||||
if (t != null) {
|
||||
if (t.MarketTrendRelation.Equals("Relative", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
}
|
||||
result += "<option" + selected + ">Relative</option>";
|
||||
|
||||
if (t != null) {
|
||||
if (t.MarketTrendRelation.Equals("Absolute", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
}
|
||||
result += "<option" + selected + ">Absolute</option>";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetOffTriggerMarketTrendRelationSelection(OffTrigger ot) {
|
||||
string result = "";
|
||||
|
||||
string selected = "";
|
||||
if (ot != null) {
|
||||
if (ot.MarketTrendRelation.Equals("Relative", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
}
|
||||
result += "<option" + selected + ">Relative</option>";
|
||||
|
||||
if (ot != null) {
|
||||
if (ot.MarketTrendRelation.Equals("RelativeTrigger", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
}
|
||||
result += "<option" + selected + " value=\"RelativeTrigger\">Relative to trigger price</option>";
|
||||
|
||||
if (ot != null) {
|
||||
if (ot.MarketTrendRelation.Equals("Absolute", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
}
|
||||
result += "<option" + selected + ">Absolute</option>";
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetValueModes(string propertyKey) {
|
||||
string result = "";
|
||||
|
||||
string selected = "";
|
||||
if (propertyKey.IndexOf("_OFFSET", StringComparison.InvariantCultureIgnoreCase) == -1) {
|
||||
selected = " selected=\"selected\"";
|
||||
}
|
||||
result += "<option" + selected + " value=\"\">Flat value</option>";
|
||||
|
||||
if (propertyKey.EndsWith("_OFFSET", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
result += "<option" + selected + " value=\"_OFFSET\">Offset by flat value</option>";
|
||||
|
||||
if (propertyKey.EndsWith("_OFFSETPERCENT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
selected = " selected=\"selected\"";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
result += "<option" + selected + " value=\"_OFFSETPERCENT\">Offset by percent</option>";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
@page
|
||||
@model TickerWidgetsModel
|
||||
@{
|
||||
Layout = null;
|
||||
|
||||
int activeSingleSettings = Model.MarketsWithSingleSettings.Count;
|
||||
string singleSettingInfoIcon = "";
|
||||
if (activeSingleSettings > 0) {
|
||||
singleSettingInfoIcon = "<i class=\"fa fa-info-circle text-muted\" data-toggle=\"tooltip\" data-placement=\"top\" data-html=\"true\" title=\"<b>Single Market Settings active for:</b><br />-" + Core.Helper.SystemHelper.ConvertListToTokenString(Model.MarketsWithSingleSettings, "<br />-", true) + "\" data-template=\"<div class='tooltip' role='tooltip'><div class='tooltip-arrow'></div><div class='tooltip-inner tooltip-200 text-left'></div></div>\"></i>";
|
||||
}
|
||||
|
||||
double currentBalance = Model.PTData.GetCurrentBalance();
|
||||
string currentBalanceString = currentBalance.ToString("#,#0.00000000", new System.Globalization.CultureInfo("en-US"));
|
||||
if (currentBalance > 100) {
|
||||
currentBalanceString = Math.Round(currentBalance, 2).ToString("#,#0.00", new System.Globalization.CultureInfo("en-US"));
|
||||
}
|
||||
|
||||
DateTime lastRuntime = Model.Summary.LastRuntime;
|
||||
double elapsedSecondsSinceRuntime = DateTime.Now.Subtract(lastRuntime).TotalSeconds;
|
||||
double intervalSeconds = Model.PTMagicConfiguration.AnalyzerSettings.MarketAnalyzer.IntervalMinutes * 60.0;
|
||||
|
||||
string iconColor = "text-success";
|
||||
string ptMagicHealthIcon = "fa-heartbeat";
|
||||
string ptMagicHealthTooltip = "PT Magic is alive and healthy!";
|
||||
if (elapsedSecondsSinceRuntime > (intervalSeconds + intervalSeconds * 0.2)) {
|
||||
ptMagicHealthIcon = "fa-exclamation-triangle";
|
||||
ptMagicHealthTooltip = "PT Magic seems to have problems, check the logs!";
|
||||
iconColor = "text-danger";
|
||||
}
|
||||
}
|
||||
|
||||
@if (!Model.Summary.MainMarket.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
<div class="card-box card-box-mini card-box-warning-outlined" data-toggle="tooltip" data-placement="bottom" title="Value of a single @Model.Summary.MainMarket">
|
||||
<b>@Model.Summary.MainMarket: </b>@Html.Raw(Model.MainFiatCurrencySymbol + Model.Summary.MainMarketPrice.ToString("#,#0.00", new System.Globalization.CultureInfo("en-US")))
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card-box card-box-mini card-box-success-outlined" data-toggle="tooltip" data-placement="bottom" title="Approximate total value in @Model.Summary.MainMarket">
|
||||
<b>TV:</b> @currentBalanceString @Model.Summary.MainMarket
|
||||
</div>
|
||||
|
||||
<div class="card-box card-box-mini card-box-ptmagic-outlined" data-toggle="tooltip" data-placement="bottom" title="Active global setting">
|
||||
<b>GS:</b> @Core.Helper.SystemHelper.SplitCamelCase(Model.Summary.CurrentGlobalSetting.SettingName)
|
||||
</div>
|
||||
|
||||
<div class="card-box card-box-mini card-box-ptmagic-outlined" data-toggle="tooltip" data-placement="bottom" title="Active single market settings">
|
||||
<b>SMS:</b> <a href="ManageSMS">@Html.Raw(singleSettingInfoIcon)</a> @activeSingleSettings
|
||||
</div>
|
||||
|
||||
<div class="card-box card-box-mini card-box-ptmagic-status-outlined @iconColor" data-toggle="tooltip" data-placement="bottom" title="@ptMagicHealthTooltip">
|
||||
<b>PTM: </b><i class="fa @ptMagicHealthIcon @iconColor"></i>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function ($) {
|
||||
'use strict';
|
||||
$('[role="tooltip"]').remove();
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('.text-autocolor').autocolor(false);
|
||||
})(jQuery);
|
||||
|
||||
</script>
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class TickerWidgetsModel : _Internal.BasePageModelSecureAJAX {
|
||||
public ProfitTrailerData PTData = null;
|
||||
public List<string> MarketsWithSingleSettings = new List<string>();
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
BindData();
|
||||
}
|
||||
|
||||
private void BindData() {
|
||||
PTData = new ProfitTrailerData(PTMagicBasePath, PTMagicConfiguration);
|
||||
|
||||
// Get markets with active single settings
|
||||
foreach (string key in Summary.MarketSummary.Keys) {
|
||||
if (Summary.MarketSummary[key].ActiveSingleSettings != null) {
|
||||
if (Summary.MarketSummary[key].ActiveSingleSettings.Count > 0) {
|
||||
MarketsWithSingleSettings.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
MarketsWithSingleSettings.Sort();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model RemoveTransactionModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class RemoveTransactionForm {
|
||||
public string Transaction_GUID = "";
|
||||
}
|
||||
|
||||
public class RemoveTransactionModel : _Internal.BasePageModelSecure {
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult OnPost() {
|
||||
base.Init();
|
||||
|
||||
JsonResult result = new JsonResult("Error removing transaction.");
|
||||
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Request.Body.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
using (StreamReader reader = new StreamReader(stream)) {
|
||||
string requestBody = reader.ReadToEnd();
|
||||
if (requestBody.Length > 0) {
|
||||
RemoveTransactionForm rtf = JsonConvert.DeserializeObject<RemoveTransactionForm>(requestBody);
|
||||
if (rtf != null) {
|
||||
TransactionData transactionData = new TransactionData(PTMagicBasePath);
|
||||
Transaction removeTransaction = transactionData.Transactions.Find(t => t.GUID.Equals(rtf.Transaction_GUID));
|
||||
if (removeTransaction != null) {
|
||||
transactionData.Transactions.Remove(removeTransaction);
|
||||
transactionData.SaveTransactions(PTMagicBasePath);
|
||||
|
||||
result = new JsonResult("Success");
|
||||
} else {
|
||||
result = new JsonResult("Transaction not found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model ResetSingleMarketSettingModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class ResetSingleMarketSettingForm {
|
||||
public string Market = "";
|
||||
public string Setting = "";
|
||||
}
|
||||
|
||||
public class ResetSingleMarketSettingModel : _Internal.BasePageModelSecure {
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult OnPost() {
|
||||
base.Init();
|
||||
|
||||
JsonResult result = new JsonResult("Error removing transaction.");
|
||||
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Request.Body.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
using (StreamReader reader = new StreamReader(stream)) {
|
||||
string requestBody = reader.ReadToEnd();
|
||||
if (requestBody.Length > 0) {
|
||||
ResetSingleMarketSettingForm rsf = JsonConvert.DeserializeObject<ResetSingleMarketSettingForm>(requestBody);
|
||||
if (rsf != null) {
|
||||
if (System.IO.File.Exists(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "SingleMarketSettingSummary.json")) {
|
||||
try {
|
||||
List<SingleMarketSettingSummary> smsSummaries = JsonConvert.DeserializeObject<List<SingleMarketSettingSummary>>(System.IO.File.ReadAllText(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "SingleMarketSettingSummary.json"));
|
||||
SingleMarketSettingSummary smsSummary = smsSummaries.Find(s => s.Market.Equals(rsf.Market) && s.SingleMarketSetting.SettingName.Equals(rsf.Setting));
|
||||
if (smsSummary != null) {
|
||||
smsSummaries.Remove(smsSummary);
|
||||
|
||||
// Save Single Market Settings Summary
|
||||
JsonSerializerSettings smsSummaryJsonSettings = new JsonSerializerSettings();
|
||||
smsSummaryJsonSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||
smsSummaryJsonSettings.DefaultValueHandling = DefaultValueHandling.Ignore;
|
||||
|
||||
FileHelper.WriteTextToFile(PTMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar, "SingleMarketSettingSummary.json", JsonConvert.SerializeObject(smsSummaries, Formatting.None, smsSummaryJsonSettings));
|
||||
|
||||
}
|
||||
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model RestoreBackupModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class RestoreBackupForm {
|
||||
public string File = "";
|
||||
}
|
||||
|
||||
public class RestoreBackupModel : _Internal.BasePageModelSecure {
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult OnPost() {
|
||||
base.Init();
|
||||
|
||||
JsonResult result = new JsonResult("Error restoring backup.");
|
||||
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Request.Body.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
using (StreamReader reader = new StreamReader(stream)) {
|
||||
string requestBody = reader.ReadToEnd();
|
||||
if (requestBody.Length > 0) {
|
||||
RestoreBackupForm rbf = JsonConvert.DeserializeObject<RestoreBackupForm>(requestBody);
|
||||
if (rbf != null) {
|
||||
if (System.IO.File.Exists(PTMagicBasePath + rbf.File)) {
|
||||
if (System.IO.File.Exists(PTMagicBasePath + rbf.File + ".backup")) {
|
||||
try {
|
||||
System.IO.File.Copy(PTMagicBasePath + rbf.File + ".backup", PTMagicBasePath + rbf.File, true);
|
||||
|
||||
result = new JsonResult("Success");
|
||||
} catch { }
|
||||
} else {
|
||||
result = new JsonResult("Error restoring backup - File '" + rbf.File + ".backup' not found in " + PTMagicBasePath + ".");
|
||||
}
|
||||
} else {
|
||||
result = new JsonResult("Error restoring backup - File '" + rbf.File + "' not found in " + PTMagicBasePath + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model SavePresetFileModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class SavePresetFileForm {
|
||||
public string FileName = "";
|
||||
public string SettingName = "";
|
||||
public string FileContent = "";
|
||||
}
|
||||
|
||||
public class SavePresetFileModel : _Internal.BasePageModelSecure {
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult OnPost() {
|
||||
base.Init();
|
||||
|
||||
JsonResult result = new JsonResult("Error saving preset file.");
|
||||
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Request.Body.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
using (StreamReader reader = new StreamReader(stream)) {
|
||||
string requestBody = reader.ReadToEnd();
|
||||
if (requestBody.Length > 0) {
|
||||
SavePresetFileForm spff = JsonConvert.DeserializeObject<SavePresetFileForm>(requestBody);
|
||||
if (spff != null) {
|
||||
string settingPropertiesPath = PTMagicBasePath + Constants.PTMagicPathPresets + Path.DirectorySeparatorChar + spff.SettingName + Path.DirectorySeparatorChar + spff.FileName;
|
||||
if (System.IO.File.Exists(settingPropertiesPath)) {
|
||||
try {
|
||||
System.IO.File.WriteAllText(settingPropertiesPath, spff.FileContent);
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@page
|
||||
@model TestTelegramModel
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Core.Main;
|
||||
using Core.Helper;
|
||||
using Core.Main.DataObjects.PTMagicData;
|
||||
using Core.MarketAnalyzer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Monitor.Pages {
|
||||
public class TestTelegramForm {
|
||||
public string Telegram_BotToken = "";
|
||||
public Int64 Telegram_ChatId = 0;
|
||||
public string Telegram_SilentMode = "off";
|
||||
}
|
||||
|
||||
public class TestTelegramModel : _Internal.BasePageModelSecure {
|
||||
|
||||
public void OnGet() {
|
||||
// Initialize Config
|
||||
base.Init();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult OnPost() {
|
||||
base.Init();
|
||||
|
||||
JsonResult result = new JsonResult("Error sending Telegram message.");
|
||||
|
||||
MemoryStream stream = new MemoryStream();
|
||||
Request.Body.CopyTo(stream);
|
||||
stream.Position = 0;
|
||||
using (StreamReader reader = new StreamReader(stream)) {
|
||||
string requestBody = reader.ReadToEnd();
|
||||
if (requestBody.Length > 0) {
|
||||
TestTelegramForm tf = JsonConvert.DeserializeObject<TestTelegramForm>(requestBody);
|
||||
if (tf != null) {
|
||||
TelegramHelper.SendMessage(tf.Telegram_BotToken.Trim(), tf.Telegram_ChatId, "PT Magic Telegram test message.", tf.Telegram_SilentMode.Equals("on"), Log);
|
||||
|
||||
result =new JsonResult("Success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Core.Main;
|
||||
|
||||
namespace Monitor {
|
||||
public class Program {
|
||||
public static void Main(string[] args) {
|
||||
Console.WriteLine("##########################################################");
|
||||
Console.WriteLine("#********************************************************#");
|
||||
Console.WriteLine("INFO: Starting PT Magic Monitor...");
|
||||
Console.WriteLine("INFO: Beginning startup checks...");
|
||||
|
||||
string monitorBasePath = Directory.GetCurrentDirectory();
|
||||
if (!System.IO.File.Exists(monitorBasePath + Path.DirectorySeparatorChar + "appsettings.json")) {
|
||||
monitorBasePath += Path.DirectorySeparatorChar + "Monitor";
|
||||
}
|
||||
|
||||
// Startup checks
|
||||
string appsettingsJson = monitorBasePath + Path.DirectorySeparatorChar + "appsettings.json";
|
||||
if (!File.Exists(appsettingsJson)) {
|
||||
Console.WriteLine("ERROR: appsettings.json not found: '" + appsettingsJson + "'. Please check if the file exists. If not, review the PT Magic setup steps listed on the wiki!");
|
||||
if (Console.KeyAvailable) Console.ReadKey();
|
||||
} else {
|
||||
Console.WriteLine("INFO: appsettings.json found in " + monitorBasePath);
|
||||
|
||||
IConfiguration config = new ConfigurationBuilder()
|
||||
.SetBasePath(monitorBasePath)
|
||||
.AddJsonFile("appsettings.json", false)
|
||||
.Build();
|
||||
|
||||
string ptMagicBasePath = config.GetValue<string>("PTMagicBasePath");
|
||||
|
||||
if (!ptMagicBasePath.EndsWith(Path.DirectorySeparatorChar)) {
|
||||
ptMagicBasePath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
// More startup checks
|
||||
// Check if PT Magic directoy is correctly configured
|
||||
if (!Directory.Exists(ptMagicBasePath)) {
|
||||
Console.WriteLine("ERROR: PT Magic directory not found: '" + ptMagicBasePath + "'. Please check your setting for 'PTMagicBasePath' in 'Monitor/appsettings.json'");
|
||||
if (Console.KeyAvailable) Console.ReadKey();
|
||||
} else {
|
||||
Console.WriteLine("INFO: PT Magic directory found at " + ptMagicBasePath);
|
||||
|
||||
// Check if PT Magic settings file exists
|
||||
string settingsGeneralJson = ptMagicBasePath + "settings.general.json";
|
||||
if (!File.Exists(settingsGeneralJson)) {
|
||||
Console.WriteLine("ERROR: PT Magic settings not found: '" + settingsGeneralJson + "'. Please check if you setup PT Magic correctly!");
|
||||
if (Console.KeyAvailable) Console.ReadKey();
|
||||
} else {
|
||||
Console.WriteLine("INFO: settings.general.json found at " + settingsGeneralJson);
|
||||
|
||||
// Check if PT Magic settings file exists
|
||||
string lastRuntimeSummaryJson = ptMagicBasePath + Constants.PTMagicPathData + Path.DirectorySeparatorChar + "LastRuntimeSummary.json";
|
||||
if (!File.Exists(lastRuntimeSummaryJson)) {
|
||||
Console.WriteLine("ERROR: PT Magic runtime summary not found: '" + lastRuntimeSummaryJson + "'. Please wait for PT Magic to complete its first run!");
|
||||
if (Console.KeyAvailable) Console.ReadKey();
|
||||
} else {
|
||||
Console.WriteLine("INFO: LastRuntimeSummary.json found at " + lastRuntimeSummaryJson);
|
||||
|
||||
PTMagicConfiguration ptMagicConfiguration = null;
|
||||
try {
|
||||
ptMagicConfiguration = new PTMagicConfiguration(ptMagicBasePath);
|
||||
} catch (Exception ex) {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
string wwwrootPath = monitorBasePath + Path.DirectorySeparatorChar + "wwwroot";
|
||||
if (!Directory.Exists(wwwrootPath)) {
|
||||
Console.WriteLine("ERROR: wwwroot directory not found: '" + wwwrootPath + "'. Did you copy all files as instructed on the wiki?");
|
||||
if (Console.KeyAvailable) Console.ReadKey();
|
||||
} else {
|
||||
Console.WriteLine("INFO: wwwroot directory found at " + wwwrootPath);
|
||||
|
||||
string assetsPath = wwwrootPath + Path.DirectorySeparatorChar + "assets";
|
||||
if (!Directory.Exists(assetsPath)) {
|
||||
Console.WriteLine("ERROR: assets directory not found: '" + assetsPath + "'. Did you copy all files as instructed on the wiki?");
|
||||
if (Console.KeyAvailable) Console.ReadKey();
|
||||
} else {
|
||||
Console.WriteLine("INFO: assets directory found at " + assetsPath);
|
||||
Console.WriteLine("INFO: ALL CHECKS COMPLETED - ATTEMPTING TO START WEBSERVER...");
|
||||
Console.WriteLine("#********************************************************#");
|
||||
Console.WriteLine("");
|
||||
Console.WriteLine("DO NOT CLOSE THIS WINDOW! THIS IS THE WEBSERVER FOR YOUR MONITOR!");
|
||||
Console.WriteLine("");
|
||||
Console.WriteLine("##########################################################");
|
||||
Console.WriteLine("");
|
||||
|
||||
BuildWebHost(args, monitorBasePath, monitorBasePath + Path.DirectorySeparatorChar + "wwwroot", ptMagicConfiguration.GeneralSettings.Monitor.Port).Run();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IWebHost BuildWebHost(string[] args, string contentRoot, string webroot, int port) =>
|
||||
new WebHostBuilder()
|
||||
.UseUrls("http://0.0.0.0:" + port.ToString())
|
||||
.UseStartup<Startup>()
|
||||
.UseKestrel()
|
||||
.UseContentRoot(contentRoot)
|
||||
.UseWebRoot(webroot)
|
||||
.Build();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue