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.Text.RegularExpressions; 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 { #region "Private methods" private static bool IsPropertyLine(string line) { Regex lineRegex = new Regex(@"^[^#][^=]+=.*$"); return lineRegex.IsMatch(line); } private static string CalculatePropertyValue(string settingProperty, string oldValueString, string newValueString, out string configPropertyKey) { int valueMode = Constants.ValueModeDefault; configPropertyKey = settingProperty.Trim(); string result = null; // Determine the mode for changing the value if (configPropertyKey.IndexOf("_OFFSETPERCENT") > -1) { valueMode = Constants.ValueModeOffsetPercent; configPropertyKey = configPropertyKey.Replace("_OFFSETPERCENT", ""); } else if (configPropertyKey.IndexOf("_OFFSET") > -1) { valueMode = Constants.ValueModeOffset; configPropertyKey = configPropertyKey.Replace("_OFFSET", ""); } // Boolean value, fix case if (newValueString.ToLower().Equals("true") || newValueString.ToLower().Equals("false")) { result = newValueString.ToLower(); } else { // Value, calculate new value switch (valueMode) { case Constants.ValueModeOffset: // Offset value by a fixed amount double offsetValue = SystemHelper.TextToDouble(newValueString, 0, "en-US"); if (offsetValue != 0) { bool ContainsPercent = false; if (oldValueString.Contains("%")) { ContainsPercent = true; oldValueString = oldValueString.Substring(0, oldValueString.Length - 1); } double oldValue = SystemHelper.TextToDouble(oldValueString, 0, "en-US"); result = Math.Round((oldValue + offsetValue), 8).ToString(new System.Globalization.CultureInfo("en-US")); if (ContainsPercent){ result = result +"%"; } } break; case Constants.ValueModeOffsetPercent: // Offset value by percentage double offsetValuePercent = SystemHelper.TextToDouble(newValueString, 0, "en-US"); if (offsetValuePercent != 0) { bool ContainsPercent = false; if (oldValueString.Contains("%")) { ContainsPercent = true; oldValueString = oldValueString.Substring(0, oldValueString.Length - 1); } double oldValue = SystemHelper.TextToDouble(oldValueString, 0, "en-US"); if (oldValue < 0) offsetValuePercent = offsetValuePercent * -1; double oldValueOffset = (oldValue * (offsetValuePercent / 100)); // Ensure integers for timeout and pairs properties, otherwise double if (configPropertyKey.Contains("timeout", StringComparison.InvariantCultureIgnoreCase) || configPropertyKey.Contains("trading_pairs", StringComparison.InvariantCultureIgnoreCase) || configPropertyKey.Contains("buy_volume", StringComparison.InvariantCultureIgnoreCase) || configPropertyKey.Contains("listed_days", StringComparison.InvariantCultureIgnoreCase) || configPropertyKey.Contains("orderbook_depth", StringComparison.InvariantCultureIgnoreCase) || configPropertyKey.Contains("rebuy_count", StringComparison.InvariantCultureIgnoreCase) || configPropertyKey.Contains("buy_volume", StringComparison.InvariantCultureIgnoreCase)) { result = ((int)(Math.Round((oldValue + oldValueOffset), MidpointRounding.AwayFromZero) + .5)).ToString(new System.Globalization.CultureInfo("en-US")); } else { // Use double to calculate result = Math.Round((oldValue + oldValueOffset), 8).ToString(new System.Globalization.CultureInfo("en-US")); } if (ContainsPercent){ result = result +"%"; } } break; default: // Raw value no processing required result = newValueString; break; } } return result; } #endregion #region "Public interface" public static string GetMainMarket(PTMagicConfiguration systemConfiguration, List 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 pairsLines, LogHelper log) { string result = ""; foreach (string line in pairsLines) { if (line.Replace(" ", "").StartsWith("enabled_pairs", StringComparison.InvariantCultureIgnoreCase)) { result = result.Replace("enabled_pairs", "", StringComparison.InvariantCultureIgnoreCase); result = result.Replace("#", ""); result = result.Replace("=", "").Trim(); break; } } return result; } public static void WriteHeaderLines(string settingsName, DateTime settingsChangeTimestamp, List lines) { // Writing Header lines lines.Insert(0, "#"); lines.Insert(0, "# ####################################"); lines.Insert(0, "# PTMagic_LastChanged = " + settingsChangeTimestamp.ToShortDateString() + " " + settingsChangeTimestamp.ToShortTimeString()); lines.Insert(0, "# PTMagic_ActiveSetting = " + SystemHelper.StripBadCode(settingsName, Constants.WhiteListProperties)); lines.Insert(0, "# ####################################"); } public static Dictionary GetPropertiesAsDictionary(List propertyLines) { Dictionary result = new Dictionary(); 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 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(PTMagic ptmagicInstance, GlobalSetting setting, DateTime settingTimestamp) { SettingsHandler.BuildPropertyLines("Pairs", ptmagicInstance, setting, settingTimestamp); SettingsHandler.BuildPropertyLines("DCA", ptmagicInstance, setting, settingTimestamp); SettingsHandler.BuildPropertyLines("Indicators", ptmagicInstance, setting, settingTimestamp); } public static void BuildPropertyLines(string fileType, PTMagic ptmagicInstance, GlobalSetting setting, DateTime settingLastChanged) { bool headerLinesExist = false; List result = new List(); List fileLines = null; // Analsye the properties for the setting and apply Dictionary properties = (Dictionary)setting.GetType().GetProperty(fileType + "Properties").GetValue(setting, null); // Building Properties if (properties == null || !properties.ContainsKey("File")) { // Load default settings as basis for the switch GlobalSetting defaultSetting = ptmagicInstance.PTMagicConfiguration.AnalyzerSettings.GlobalSettings.Find(a => a.SettingName.Equals(ptmagicInstance.DefaultSettingName, StringComparison.InvariantCultureIgnoreCase)); Dictionary defaultProperties = (Dictionary)defaultSetting.GetType().GetProperty(fileType + "Properties").GetValue(defaultSetting, null); if (defaultProperties.ContainsKey("File")) { // Load the default settings file lines fileLines = SettingsFiles.GetPresetFileLinesAsList(defaultSetting.SettingName, defaultProperties["File"].ToString(), ptmagicInstance.PTMagicConfiguration); } else { // No preset file defined, this is a bad settings file! throw new ApplicationException(string.Format("No 'File' setting found in the '{0}Properties' of the 'Default' setting section in the 'settings.analyzer.json' file; this must be defined!", fileType)); } } else { // Settings are configured in a seperate file fileLines = SettingsFiles.GetPresetFileLinesAsList(setting.SettingName, properties["File"].ToString(), ptmagicInstance.PTMagicConfiguration); } // Check for PTM header in preset file // Loop through config line by line reprocessing where required. foreach (string line in fileLines) { if (line.IndexOf("PTMagic_ActiveSetting", StringComparison.InvariantCultureIgnoreCase) > -1) { // Setting current active setting result.Add("# PTMagic_ActiveSetting = " + setting.SettingName); headerLinesExist = true; } else if (line.IndexOf("PTMagic_LastChanged", StringComparison.InvariantCultureIgnoreCase) > -1) { // Setting last change datetime result.Add("# PTMagic_LastChanged = " + settingLastChanged.ToShortDateString() + " " + settingLastChanged.ToShortTimeString()); } else if (line.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) { // Single Market Settings will get overwritten every single run => crop the lines break; } else if (IsPropertyLine(line)) { // We have got a property line if (properties != null) { bool madeSubstitution = false; foreach (string settingProperty in properties.Keys) { if (madeSubstitution) { // We've made a substitution so no need to process the rest of the properties break; } else { madeSubstitution = SettingsHandler.BuildPropertyLine(result, setting.SettingName, line, properties, settingProperty); } } if (!madeSubstitution) { // No substitution made, so simply copy the line result.Add(line); } } } else { // Non property line, just copy it result.Add(line); } } // Write header lines if required if (!headerLinesExist) { WriteHeaderLines(setting.SettingName, settingLastChanged, result); } // Save lines to current context for the file type ptmagicInstance.GetType().GetProperty(fileType + "Lines").SetValue(ptmagicInstance, result); } public static bool BuildPropertyLine(List result, string settingName, string line, Dictionary properties, string settingProperty) { bool madeSubstitutions = false; string propertyKey; var lineParts = line.Trim().Split("="); string linePropertyName = lineParts[0].Trim(); string newValueString = SystemHelper.PropertyToString(properties[settingProperty]); string oldValueString = lineParts[1].Trim(); newValueString = CalculatePropertyValue(settingProperty, oldValueString, newValueString, out propertyKey); if (linePropertyName.Equals(propertyKey, StringComparison.InvariantCultureIgnoreCase)) { madeSubstitutions = true; line = propertyKey + " = " + newValueString; string previousLine = result.Last(); if (previousLine.IndexOf("PTMagic changed line", StringComparison.InvariantCultureIgnoreCase) > -1) { result.RemoveAt(result.Count - 1); } else { result.Add(String.Format("# PTMagic changed {5} for setting '{0}' from value '{1}' to '{2}' on {3} {4}", settingName, oldValueString, newValueString, DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), linePropertyName)); } result.Add(line); } return madeSubstitutions; } public static List> CompileSingleMarketProperties(PTMagic ptmagicInstance, Dictionary> matchedTriggers) { try { List> smsApplied = new List>(); List globalPairsLines = new List(); List globalDCALines = new List(); List globalIndicatorsLines = new List(); List newPairsLines = new List(); List newDCALines = new List(); List newIndicatorsLines = new List(); // Find the previous single market settings section in the pairs file foreach (string pairsLine in ptmagicInstance.PairsLines) { if (pairsLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) { // Single Market Settings will get overwritten every single run => crop the lines break; } else { // Copy the line into the new pairs file and keep searching string globalPairsLine = pairsLine; globalPairsLines.Add(globalPairsLine); } } newPairsLines.Add("# PTMagic_SingleMarketSettings - Written on " + DateTime.Now.ToString()); newPairsLines.Add("# ########################################################################"); newPairsLines.Add("#"); // Find the previous single market settings section in the DCA file foreach (string dcaLine in ptmagicInstance.DCALines) { if (dcaLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) { // Single Market Settings will get overwritten every single run => crop the lines break; } else { // Copy the line into the new pairs file and keep searching string globalDCALine = dcaLine; globalDCALines.Add(globalDCALine); } } newDCALines.Add("# PTMagic_SingleMarketSettings - Written on " + DateTime.Now.ToString()); newDCALines.Add("# ########################################################################"); newDCALines.Add("#"); // Find the previous single market settings section in the Indicators file foreach (string indicatorsLine in ptmagicInstance.IndicatorsLines) { if (indicatorsLine.IndexOf("PTMagic_SingleMarketSettings", StringComparison.InvariantCultureIgnoreCase) > -1) { // Single Market Settings will get overwritten every single run => crop the lines break; } else { // Copy the line into the new pairs file and keep searching string globalIndicatorsLine = indicatorsLine; globalIndicatorsLines.Add(globalIndicatorsLine); } } Dictionary globalPairsProperties = SettingsHandler.GetPropertiesAsDictionary(globalPairsLines); Dictionary globalDCAProperties = SettingsHandler.GetPropertiesAsDictionary(globalDCALines); Dictionary globalIndicatorsProperties = SettingsHandler.GetPropertiesAsDictionary(globalIndicatorsLines); newIndicatorsLines.Add("# PTMagic_SingleMarketSettings - Written on " + DateTime.Now.ToString()); newIndicatorsLines.Add("# ########################################################################"); newIndicatorsLines.Add("#"); foreach (string marketPair in ptmagicInstance.TriggeredSingleMarketSettings.Keys.OrderBy(k => k)) { Dictionary pairsPropertiesToApply = new Dictionary(); Dictionary dcaPropertiesToApply = new Dictionary(); Dictionary indicatorsPropertiesToApply = new Dictionary(); // 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 ptmagicInstance.TriggeredSingleMarketSettings[marketPair]) { ptmagicInstance.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]; } } ptmagicInstance.Log.DoLogInfo("Built single market settings '" + setting.SettingName + "' for '" + marketPair + "'."); smsApplied.Add(new KeyValuePair(marketPair, setting.SettingName)); } newPairsLines = SettingsHandler.BuildPropertyLinesForSingleMarketSetting(ptmagicInstance.LastRuntimeSummary.MainMarket, marketPair, ptmagicInstance.TriggeredSingleMarketSettings[marketPair], pairsPropertiesToApply, matchedTriggers, globalPairsProperties, newPairsLines, ptmagicInstance.PTMagicConfiguration, ptmagicInstance.Log); newDCALines = SettingsHandler.BuildPropertyLinesForSingleMarketSetting(ptmagicInstance.LastRuntimeSummary.MainMarket, marketPair, ptmagicInstance.TriggeredSingleMarketSettings[marketPair], dcaPropertiesToApply, matchedTriggers, globalDCAProperties, newDCALines, ptmagicInstance.PTMagicConfiguration, ptmagicInstance.Log); newIndicatorsLines = SettingsHandler.BuildPropertyLinesForSingleMarketSetting(ptmagicInstance.LastRuntimeSummary.MainMarket, marketPair, ptmagicInstance.TriggeredSingleMarketSettings[marketPair], indicatorsPropertiesToApply, matchedTriggers, globalIndicatorsProperties, newIndicatorsLines, ptmagicInstance.PTMagicConfiguration, ptmagicInstance.Log); } // Combine global settings lines with single market settings lines globalPairsLines.AddRange(newPairsLines); globalDCALines.AddRange(newDCALines); globalIndicatorsLines.AddRange(newIndicatorsLines); ptmagicInstance.PairsLines = globalPairsLines; ptmagicInstance.DCALines = globalDCALines; ptmagicInstance.IndicatorsLines = globalIndicatorsLines; return smsApplied; } catch (Exception ex) { ptmagicInstance.Log.DoLogCritical("Critical error while writing settings!", ex); throw (ex); } } public static List BuildPropertyLinesForSingleMarketSetting(string mainMarket, string marketPair, List appliedSettings, Dictionary properties, Dictionary> matchedTriggers, Dictionary fullProperties, List 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); foreach (string settingProperty in properties.Keys) { string propertyKey = settingProperty; string propertyKeyName = propertyKey.Replace("_OFFSETPERCENT", ""); propertyKeyName = propertyKeyName.Replace("_OFFSET", ""); string newValueString = SystemHelper.PropertyToString(properties[settingProperty]); string oldValueString = SettingsHandler.GetCurrentPropertyValue(fullProperties, propertyKeyName, propertyKeyName.Replace("ALL_", "DEFAULT_")); newValueString = CalculatePropertyValue(settingProperty, oldValueString, newValueString, out propertyKey); string propertyMarketName = marketPair; // Adjust market pair name 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; } } newPropertyLines.Add(propertyKeyString + " = " + newValueString); } } return newPropertyLines; } public static bool RemoveSingleMarketSettings(PTMagic ptmagicInstance) { bool result = false; try { List cleanedUpPairsLines = new List(); List cleanedUpDCALines = new List(); List cleanedUpIndicatorsLines = new List(); bool removedPairsSingleMarketSettings = false; foreach (string pairsLine in ptmagicInstance.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 ptmagicInstance.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 ptmagicInstance.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); } } ptmagicInstance.PairsLines = cleanedUpPairsLines; ptmagicInstance.DCALines = cleanedUpDCALines; ptmagicInstance.IndicatorsLines = cleanedUpIndicatorsLines; result = removedPairsSingleMarketSettings && removedDCASingleMarketSettings && removedIndicatorsSingleMarketSettings; } catch (Exception ex) { ptmagicInstance.Log.DoLogCritical("Critical error while writing settings!", ex); } return result; } #endregion } }