From cf4ad83faad8099c93ee606c21bef6f81fb15cce Mon Sep 17 00:00:00 2001 From: Crynners Date: Thu, 29 Jul 2021 17:53:09 +0200 Subject: [PATCH] AccBot 1.0 --- CryptoBotCore.sln | 31 + CryptoBotCore/API/CoinmateAPI.cs | 756 ++++++++++++++++++ CryptoBotCore/API/CryptoExchangeAPI.cs | 58 ++ CryptoBotCore/API/CurrencyExchangeAPI.cs | 67 ++ CryptoBotCore/AppSettings.json | 2 + CryptoBotCore/Bot.cs | 91 +++ .../BotStrategies/AccumulationBot.cs | 208 +++++ CryptoBotCore/CosmosDB/CosmosDbContext.cs | 136 ++++ .../CosmosDB/Model/AccumulationSummary.cs | 17 + CryptoBotCore/CryptoBotCore.csproj | 27 + CryptoBotCore/Models/BotConfiguration.cs | 21 + CryptoBotCore/Models/CoinMateCredentials.cs | 22 + CryptoBotCore/Models/Credit.cs | 20 + CryptoBotCore/Models/Currency.cs | 12 + CryptoBotCore/Models/GenericRequest.cs | 14 + CryptoBotCore/Models/MarketPriceSnapshot.cs | 28 + CryptoBotCore/Models/MessageTypeEnum.cs | 10 + CryptoBotCore/Models/NewOrderRequest.cs | 99 +++ CryptoBotCore/Models/Offer.cs | 72 ++ CryptoBotCore/Models/OpenOrder.cs | 77 ++ CryptoBotCore/Models/OrderStatusResponse.cs | 75 ++ CryptoBotCore/Models/Platform.cs | 14 + CryptoBotCore/Program.cs | 28 + CryptoBotCore/Utility.cs | 38 + CryptoBotFunction/.gitignore | 264 ++++++ CryptoBotFunction/AccumulationBotFunction.cs | 51 ++ CryptoBotFunction/CryptoBotFunction.csproj | 21 + .../CrBts - Zip Deploy/profile.arm.json | 175 ++++ .../Properties/serviceDependencies.json | 7 + .../Properties/serviceDependencies.local.json | 7 + CryptoBotFunction/host.json | 11 + 31 files changed, 2459 insertions(+) create mode 100644 CryptoBotCore.sln create mode 100644 CryptoBotCore/API/CoinmateAPI.cs create mode 100644 CryptoBotCore/API/CryptoExchangeAPI.cs create mode 100644 CryptoBotCore/API/CurrencyExchangeAPI.cs create mode 100644 CryptoBotCore/AppSettings.json create mode 100644 CryptoBotCore/Bot.cs create mode 100644 CryptoBotCore/BotStrategies/AccumulationBot.cs create mode 100644 CryptoBotCore/CosmosDB/CosmosDbContext.cs create mode 100644 CryptoBotCore/CosmosDB/Model/AccumulationSummary.cs create mode 100644 CryptoBotCore/CryptoBotCore.csproj create mode 100644 CryptoBotCore/Models/BotConfiguration.cs create mode 100644 CryptoBotCore/Models/CoinMateCredentials.cs create mode 100644 CryptoBotCore/Models/Credit.cs create mode 100644 CryptoBotCore/Models/Currency.cs create mode 100644 CryptoBotCore/Models/GenericRequest.cs create mode 100644 CryptoBotCore/Models/MarketPriceSnapshot.cs create mode 100644 CryptoBotCore/Models/MessageTypeEnum.cs create mode 100644 CryptoBotCore/Models/NewOrderRequest.cs create mode 100644 CryptoBotCore/Models/Offer.cs create mode 100644 CryptoBotCore/Models/OpenOrder.cs create mode 100644 CryptoBotCore/Models/OrderStatusResponse.cs create mode 100644 CryptoBotCore/Models/Platform.cs create mode 100644 CryptoBotCore/Program.cs create mode 100644 CryptoBotCore/Utility.cs create mode 100644 CryptoBotFunction/.gitignore create mode 100644 CryptoBotFunction/AccumulationBotFunction.cs create mode 100644 CryptoBotFunction/CryptoBotFunction.csproj create mode 100644 CryptoBotFunction/Properties/ServiceDependencies/CrBts - Zip Deploy/profile.arm.json create mode 100644 CryptoBotFunction/Properties/serviceDependencies.json create mode 100644 CryptoBotFunction/Properties/serviceDependencies.local.json create mode 100644 CryptoBotFunction/host.json diff --git a/CryptoBotCore.sln b/CryptoBotCore.sln new file mode 100644 index 0000000..30cd9a1 --- /dev/null +++ b/CryptoBotCore.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30717.126 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CryptoBotCore", "CryptoBotCore\CryptoBotCore.csproj", "{DA2615CF-76EF-4EF8-8AED-730533BD795E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CryptoBotFunction", "CryptoBotFunction\CryptoBotFunction.csproj", "{8631C9AA-A4E5-4257-8BA2-8396CC698D7E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DA2615CF-76EF-4EF8-8AED-730533BD795E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA2615CF-76EF-4EF8-8AED-730533BD795E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA2615CF-76EF-4EF8-8AED-730533BD795E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA2615CF-76EF-4EF8-8AED-730533BD795E}.Release|Any CPU.Build.0 = Release|Any CPU + {8631C9AA-A4E5-4257-8BA2-8396CC698D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8631C9AA-A4E5-4257-8BA2-8396CC698D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8631C9AA-A4E5-4257-8BA2-8396CC698D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8631C9AA-A4E5-4257-8BA2-8396CC698D7E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EA0E7683-FE10-4E30-8F3A-BF10CA9339A2} + EndGlobalSection +EndGlobal diff --git a/CryptoBotCore/API/CoinmateAPI.cs b/CryptoBotCore/API/CoinmateAPI.cs new file mode 100644 index 0000000..dc3c09e --- /dev/null +++ b/CryptoBotCore/API/CoinmateAPI.cs @@ -0,0 +1,756 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Text.Json; +using CryptoBotCore.Models; +using Newtonsoft.Json; +using Microsoft.Extensions.Logging; + +namespace CryptoBotCore.API +{ + [Serializable] + public class CoinmateAPI : CryptoExchangeAPI + { + private int clientId { get; set; } + private string publicKey { get; set; } + private string privateKey { get; set; } + + //private static long nonce { get; set; } + static object nonceLock = new object(); + public ILogger Log { get; set; } + private string pair { get; set; } + public string pair_base { get; set; } + public string pair_quote { get; set; } + + static Tuple> LimitAmountTuple = new Tuple>(new DateTime(1900, 1, 1), null); + + public CoinmateAPI(string pair, CoinMateCredentials credentials, ILogger log) + { + this.pair = pair; + this.pair_base = pair.Split('_')[1]; + this.pair_quote = pair.Split('_')[0]; + + this.Log = log; + + clientId = credentials.ClientId; + publicKey = credentials.PublicKey; + privateKey = credentials.PrivateKey; + + //nonce = (long)Utility.getUnixTimestamps(DateTime.UtcNow); + + } + + private string getSignature(Double time) + { + Encoding ascii = Encoding.ASCII; + string message = time.ToString() + clientId.ToString() + publicKey; + HMACSHA256 hmac = new HMACSHA256(ascii.GetBytes(privateKey)); + byte[] data = hmac.ComputeHash(ascii.GetBytes(message)); + return BitConverter.ToString(data).Replace("-", string.Empty); + } + + [Serializable] + class Nonce + { + public long Number { get; set; } + + public Nonce() + { + Number = (long)Utility.getUnixTimestamps(DateTime.UtcNow); + } + } + + public static long getNonce() + { + lock (nonceLock) + { + Nonce nonce = new Nonce(); + return nonce.Number; + } + } + + public string getSecuredHeaderPart() + { + Double nonce = getNonce(); + return "clientId=" + clientId + "&publicKey=" + publicKey + "&nonce=" + nonce + "&signature=" + getSignature(nonce); + } + + // Dle CoinMate Supportu toto není validní a funguje to jen v případě, kdy máme jeden klíč + //public string getSecuredHeaderPartWithoutPublicKey() + //{ + // long nonce = getNonce(); + // return "clientId=" + clientId + "&nonce=" + nonce + "&signature=" + getSignature(nonce); + //} + + + public string buy(double amount, double exchangeRate, bool isReal, OrderType orderType = OrderType.ExchangeLimit) + { + exchangeRate = Math.Round(exchangeRate, 1); + + int wait = 0; + do + { + try + { + if (isReal) + { + if (orderType == OrderType.ExchangeMarket) + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + + //amount = Math.Floor(amount); + + string body = "total=" + amount + "¤cyPair=" + pair + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/buyInstant", body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + Log.LogError(result.errorMessage.ToString()); + throw new Exception(result.errorMessage.ToString()); + } + + return result.data; + } + else + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + + string body = "amount=" + amount + "¤cyPair=" + pair + "&price=" + exchangeRate + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/buyLimit", body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + Log.LogError(result.errorMessage.ToString()); + throw new Exception(result.errorMessage.ToString()); + } + + return result.data; + } + } + else + { + return Utility.getUnixTimestamps(DateTime.UtcNow).ToString(); + } + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public Tuple getActualExchangeRate() + { + return getActualExchangeRate(this.pair); + } + + public Tuple getActualExchangeRate(string pair) + { + int wait = 0; + do + { + try + { + WebClient client = new WebClient(); + string response = client.DownloadString("https://coinmate.io/api/ticker?currencyPair=" + pair); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return new Tuple(result.data.ask, result.data.bid); + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public double getBTCWidthrawalFee() + { + int wait = 0; + do + { + try + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string body = getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/bitcoinWithdrawalFees", body); + BTCWidthrawalFee_RootObject result = JsonConvert.DeserializeObject(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return result.data.low; + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public class BTCWidthrawalFee_Data + { + public double low { get; set; } + public double high { get; set; } + public long timestamp { get; set; } + } + + public class BTCWidthrawalFee_RootObject + { + public bool error { get; set; } + public object errorMessage { get; set; } + public BTCWidthrawalFee_Data data { get; set; } + } + + public string sell(double amount, double exchangeRate, bool isReal, OrderType orderType = OrderType.ExchangeLimit) + { + exchangeRate = Math.Round(exchangeRate, 1); + + int wait = 0; + do + { + try + { + if (isReal) + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string body = "amount=" + amount + "¤cyPair=" + this.pair + "&price=" + exchangeRate + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/sellLimit", body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return result.data; + } + else + { + return Utility.getUnixTimestamps(DateTime.UtcNow).ToString(); + } + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + + internal void withdrawBTC(double BTC, string destinationAddress) + { + int wait = 0; + do + { + try + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + + string body = "amount=" + BTC + "&address=" + destinationAddress + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/bitcoinWithdrawal", body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return; + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + internal void withdraw(double amount, string destinationAddress) + { + int wait = 0; + do + { + try + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + + string keypair = pair == "BTC_CZK" ? "bitcoinWithdrawal" : + pair == "LTC_CZK" ? "litecoinWithdrawal" : + pair == "ETH_CZK" ? "ethereumWithdrawal" : + pair == "DASH_CZK" ? "dashWithdrawal" : + null; + + string fee_priority = pair == "BTC_CZK" ? "&feePriority=LOW" : ""; + + string body = "amount=" + amount + "&address=" + destinationAddress + fee_priority + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/" + keypair, body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return; + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public List getAllOpenOrders(bool isReal) + { + int wait = 0; + do + { + try + { + if (isReal) + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string body = "currencyPair=" + this.pair + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/openOrders", body); + Response> result = JsonConvert.DeserializeObject>>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return result.data; + } + else + { + //TODO - tady musim projit buying packages a podivat, jestli uz nejsou po expiraci nejake nabidky + return null; + } + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public void getLimits() + { + if ((DateTime.Now - LimitAmountTuple.Item1).TotalMinutes > 15) + { + DateTime now = DateTime.Now; + LimitAmountTuple = new Tuple>(now, getActualLimits()); + } + } + + public class CoinMatePairConfiguration + { + public string name { get; set; } + public string firstCurrency { get; set; } + public string secondCurrency { get; set; } + public int priceDecimals { get; set; } + public int lotDecimals { get; set; } + public double minAmount { get; set; } + public string tradesWebSocketChannelId { get; set; } + public string orderBookWebSocketChannelId { get; set; } + public string tradeStatisticsWebSocketChannelId { get; set; } + } + + private Dictionary getActualLimits() + { + int wait = 0; + do + { + try + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string response = client.DownloadString("https://coinmate.io/api/tradingPairs"); + Response> result = JsonConvert.DeserializeObject>>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return result.data.ToDictionary(x => x.name, x => x.minAmount); + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + /// + /// Cancel order. Method returns remainingAmount of the order + /// + public Boolean cancelOrder(string orderId) + { + int wait = 0; + do + { + try + { + + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string body = "orderId=" + orderId + "&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/cancelOrder", body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return result.data; + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public List getMyTrades() + { + int wait = 0; + do + { + try + { + + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string body = "offset=0&limit=1000&sort=DESC&" + getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/transactionHistory", body); + Response> result = JsonConvert.DeserializeObject>>(response); + + if (result.error) + { + throw new Exception(result.errorMessage.ToString()); + } + + return result.data; + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public List getBalances() + { + int wait = 0; + do + { + try + { + WebClient client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + string body = getSecuredHeaderPart(); + string response = client.UploadString("https://coinmate.io/api/balances", body); + Response result = JsonConvert.DeserializeObject>(response); + + if (result.error) + { + Log.LogError(result.errorMessage.ToString()); + throw new Exception(result.errorMessage.ToString()); + } + + List wallets = new List(); + + wallets.Add(new WalletBalances("CZK", result.data.CZK.balance, result.data.CZK.available, "exchange")); + wallets.Add(new WalletBalances("EUR", result.data.EUR.balance, result.data.EUR.available, "exchange")); + wallets.Add(new WalletBalances("BTC", result.data.BTC.balance, result.data.BTC.available, "exchange")); + wallets.Add(new WalletBalances("LTC", result.data.LTC.balance, result.data.LTC.available, "exchange")); + wallets.Add(new WalletBalances("ETH", result.data.ETH.balance, result.data.ETH.available, "exchange")); + wallets.Add(new WalletBalances("DSH", result.data.DASH.balance, result.data.DASH.available, "exchange")); + + return wallets; + } + catch (Exception ex) + { + Log.LogError(JsonConvert.SerializeObject(ex)); + + wait = (wait == 0) ? 200 : wait * 2; + Thread.Sleep(wait); + } + } while (true); + } + + public double getLimitAmount() + { + getLimits(); + + if (!LimitAmountTuple.Item2.ContainsKey(this.pair)) + { + throw new NotImplementedException("Neexistujici par definovany pro limit"); + } + + return LimitAmountTuple.Item2[this.pair]; + } + + public void refresh() + { + getLimits(); + } + + public bool transfer(double amount, string currency, string walletfrom, string walletto) + { + throw new NotImplementedException(); + } + + //not in down + public double getATH() + { + throw new NotImplementedException(); + } + + //not in down + public List getOffers() + { + throw new NotImplementedException(); + } + + //not in down + public string newOffer(double amount, string currency, double rate, int period) + { + throw new NotImplementedException(); + } + + //not in down + public void cancelOffer(Offer offer) + { + throw new NotImplementedException(); + } + + //not in down + public List getCredits() + { + throw new NotImplementedException(); + } + + + public int getNumberOfAllOpenOrdersOnPair() + { + return getAllOpenOrders(true).Count(); + } + + + public class Response + { + public bool error { get; set; } + public object errorMessage { get; set; } + public T data { get; set; } + } + + public class Orders + { + /// + /// Buys + /// + public List asks { get; set; } + /// + /// Sells + /// + public List bids { get; set; } + public int timestamp { get; set; } + } + + public class Order + { + public double price { get; set; } + public double amount { get; set; } + } + + public class ActualExchangeRates + { + public double last { get; set; } + public double high { get; set; } + public double low { get; set; } + public double amount { get; set; } + public double bid { get; set; } + public double ask { get; set; } + public int timestamp { get; set; } + } + + public class BalanceCurrency + { + public string currency { get; set; } + public double balance { get; set; } + public double reserved { get; set; } + public double available { get; set; } + } + + public class BalanceData + { + public BalanceCurrency EUR { get; set; } + public BalanceCurrency CZK { get; set; } + public BalanceCurrency BTC { get; set; } + public BalanceCurrency LTC { get; set; } + public BalanceCurrency ETH { get; set; } + public BalanceCurrency DASH { get; set; } + } + + [Serializable] + public class Transaction + { + public object timestamp { get; set; } + public string transactionId { get; set; } + public string transactionType { get; set; } + public double? price { get; set; } + public string priceCurrency { get; set; } + public double amount { get; set; } + public string amountCurrency { get; set; } + public double? fee { get; set; } + public string feeCurrency { get; set; } + public object description { get; set; } + public string status { get; set; } + public string orderId { get; set; } + + public Transaction(Double timestamp, string type, string orderId, string transactionId, Double amount, Double price, double fee, string feeCurrency) + { + this.transactionType = type.ToUpper(); + this.orderId = orderId; + this.transactionId = transactionId; + this.amount = amount; + this.price = price; + this.fee = fee; + this.feeCurrency = feeCurrency; + this.timestamp = timestamp; + } + + public Transaction() { } + + public override string ToString() + { + return "timestamp: " + timestamp + "," + Environment.NewLine + + "transactionId: " + transactionId + "," + Environment.NewLine + + "transactionType: " + transactionType + "," + Environment.NewLine + + "price: " + price + "," + Environment.NewLine + + "priceCurrency: " + priceCurrency + "," + Environment.NewLine + + "amount: " + amount + "," + Environment.NewLine + + "amountCurrency: " + amountCurrency + "," + Environment.NewLine + + "fee: " + fee + "," + Environment.NewLine + + "feeCurrency: " + feeCurrency + "," + Environment.NewLine + + "description: " + description + "," + Environment.NewLine + + "status: " + status + "," + Environment.NewLine + + "orderId: " + orderId + "," + Environment.NewLine + + "---------------------------------------------------" + Environment.NewLine; + } + } + + public class TransactionString + { + public object timestamp { get; set; } + public string transactionId { get; set; } + public string transactionType { get; set; } + public double? price { get; set; } + public string priceCurrency { get; set; } + public double? amount { get; set; } + public string amountCurrency { get; set; } + public double? fee { get; set; } + public string feeCurrency { get; set; } + public object description { get; set; } + public string status { get; set; } + public string orderId { get; set; } + + } + + public class OrderBook_Ask + { + public double price { get; set; } + public double amount { get; set; } + } + + public class OrderBook_Bid + { + public double price { get; set; } + public double amount { get; set; } + } + + public class OrderBook + { + public List asks { get; set; } + public List bids { get; set; } + public int timestamp { get; set; } + } + + [Serializable] + public class WalletBalances + { + + public WalletBalances(string currency, Double amount, Double available, string type) + { + this.currency = currency; + this.amount = amount; + this.available = available; + this.type = type; + } + + public string type { get; set; } + public string currency { get; set; } + /// + /// How much balance of this currency in this wallet + /// + public Double amount { get; set; } + /// + /// How much X there is in this wallet that is available to trade + /// + public Double available { get; set; } + } + + } +} diff --git a/CryptoBotCore/API/CryptoExchangeAPI.cs b/CryptoBotCore/API/CryptoExchangeAPI.cs new file mode 100644 index 0000000..41afdab --- /dev/null +++ b/CryptoBotCore/API/CryptoExchangeAPI.cs @@ -0,0 +1,58 @@ +using CryptoBotCore.Models; +using System; +using System.Collections.Generic; +using static CryptoBotCore.API.CoinmateAPI; + +namespace CryptoBotCore.API +{ + public interface CryptoExchangeAPI + { + /// + /// Method which gets best exchange rates for buying and selling BTC + /// + /// + Tuple getActualExchangeRate(); + + /// + /// Returns pair ATH + /// + /// + double getATH(); + + /// + /// Method represents buying of BTC + /// + /// Amount of FIAT currency + /// Exchange rate for 1 BTC (in FIAT currency) + string buy(double amount, double exchangeRate, bool isReal, OrderType orderType); + + /// + /// Method represents selling of BTC + /// + /// Amount of BTC + /// Exchange rate for 1 BTC (in FIAT currency) + string sell(double amount, double exchangeRate, bool isReal, OrderType orderType); + + List getMyTrades(); + + List getAllOpenOrders(bool isReal); + + bool cancelOrder(string order_id); + + Double getLimitAmount(); + + void refresh(); + + bool transfer(double amount, string currency, string walletfrom, string walletto); + + List getOffers(); + + string newOffer(double amount, string currency, double rate, int period); + + void cancelOffer(Offer offer); + + List getCredits(); + + int getNumberOfAllOpenOrdersOnPair(); + } +} diff --git a/CryptoBotCore/API/CurrencyExchangeAPI.cs b/CryptoBotCore/API/CurrencyExchangeAPI.cs new file mode 100644 index 0000000..d180fdd --- /dev/null +++ b/CryptoBotCore/API/CurrencyExchangeAPI.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace CryptoBotCore.API +{ + [Serializable] + class CurrencyExchangeAPI + { + public static string USD = "USD"; + + public Double getCZK(string currencyBase) + { + WebClient client = new WebClient(); + string response = client.DownloadString("http://api.fixer.io/latest?base=" + currencyBase); + Rates result = JsonSerializer.Deserialize(response).rates; + + return result.CZK; + } + + public class Rates + { + public double AUD { get; set; } + public double BGN { get; set; } + public double BRL { get; set; } + public double CAD { get; set; } + public double CHF { get; set; } + public double CNY { get; set; } + public double CZK { get; set; } + public double DKK { get; set; } + public double GBP { get; set; } + public double HKD { get; set; } + public double HRK { get; set; } + public double HUF { get; set; } + public double IDR { get; set; } + public double ILS { get; set; } + public double INR { get; set; } + public double JPY { get; set; } + public double KRW { get; set; } + public double MXN { get; set; } + public double MYR { get; set; } + public double NOK { get; set; } + public double NZD { get; set; } + public double PHP { get; set; } + public double PLN { get; set; } + public double RON { get; set; } + public double RUB { get; set; } + public double SEK { get; set; } + public double SGD { get; set; } + public double THB { get; set; } + public double TRY { get; set; } + public double ZAR { get; set; } + public double EUR { get; set; } + } + + public class GetRates + { + public string @base { get; set; } + public string date { get; set; } + public Rates rates { get; set; } + } + } +} diff --git a/CryptoBotCore/AppSettings.json b/CryptoBotCore/AppSettings.json new file mode 100644 index 0000000..8593c62 --- /dev/null +++ b/CryptoBotCore/AppSettings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/CryptoBotCore/Bot.cs b/CryptoBotCore/Bot.cs new file mode 100644 index 0000000..f38b360 --- /dev/null +++ b/CryptoBotCore/Bot.cs @@ -0,0 +1,91 @@ +using CryptoBotCore.API; +using CryptoBotCore.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoBotCore +{ + public class Bot : BackgroundService + { + [NonSerialized] + private CoinmateAPI cryptoExchangeAPI; + private List sellOrderTransaction { get; set; } + + private List buyOrderTransaction { get; set; } + + private List openSellOrder { get; set; } + + private List openBuyOrder { get; set; } + + private ILogger _Log { get; set; } + + + private readonly IConfiguration _configuration; + + + public Bot(IConfiguration configuration, ILogger log) + { + _configuration = configuration; + //cryptoExchangeAPI = new CoinmateAPI() + } + + public override Task StartAsync(CancellationToken cancellationToken) + { + return base.StartAsync(cancellationToken); + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + return base.StopAsync(cancellationToken); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while(true) + { + await Tick(); + await Task.Delay(30000); + } + } + + //public CoinMateCredentials OnGet() + //{ + // var credentials = new CoinMateCredentials() + // { + // ClientId = Int32.Parse(_configuration["CoinMate:API_ClientId"]), + // PrivateKey = _configuration["CoinMate:API_PrivateKey"], + // PublicKey = _configuration["CoinMate:API_PublicKey"] + // }; + + // return credentials; + //} + + public async Task Tick() + { + try + { + if (cryptoExchangeAPI == null) + { + cryptoExchangeAPI = new CoinmateAPI("BTC_CZK", BotConfiguration.CoinMateCredentials, _Log); + } + + Tuple newBuySell = cryptoExchangeAPI.getActualExchangeRate(); + double newMID = (newBuySell.Item1 + newBuySell.Item2) / 2; + + var wdrfee = cryptoExchangeAPI.getBTCWidthrawalFee(); + + Console.WriteLine(wdrfee * newMID); + } + catch(Exception ex) + { + Console.WriteLine(ex); + } + } + } +} diff --git a/CryptoBotCore/BotStrategies/AccumulationBot.cs b/CryptoBotCore/BotStrategies/AccumulationBot.cs new file mode 100644 index 0000000..3e0846e --- /dev/null +++ b/CryptoBotCore/BotStrategies/AccumulationBot.cs @@ -0,0 +1,208 @@ +using CryptoBotCore.API; +using CryptoBotCore.CosmosDB; +using CryptoBotCore.Models; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Telegram.Bot; + +namespace CryptoBotCore.BotStrategies +{ + public class AccumulationBot + { + Double packageSize { get; set; } + + private static readonly string CONST_ASTERIX = "*"; + + private static readonly int ROUND_NUMBER = 7; + + [NonSerialized] + private Dictionary coinmateAPIs; + + TelegramBotClient bot = new TelegramBotClient(BotConfiguration.TelegramBot); + + private CosmosDbContext _cosmosDbContext; + + ILogger Log; + + public AccumulationBot(ILogger log) + { + Log = log; + } + + + public async Task Tick() + { + + try + { + + StringBuilder sbInformationMessage = new StringBuilder(); + Log.LogInformation("Start Tick: " + DateTime.Now); + + var pair = $"{BotConfiguration.Currency}_CZK"; + + if (coinmateAPIs == null) + { + inicializeAPI(BotConfiguration.Currency); + } + + var initBalance = coinmateAPIs[BotConfiguration.Currency].getBalances(); + + double CZK = initBalance.Where(x => x.currency == "CZK").Sum(x => x.available); + + + + Dictionary sb_actions = new Dictionary(); + + + if (CZK > BotConfiguration.ChunkSize) + { + var response = coinmateAPIs[BotConfiguration.Currency].buy(BotConfiguration.ChunkSize, 0, true, OrderType.ExchangeMarket); + + Log.LogInformation($"Market buy {BotConfiguration.Currency} for {BotConfiguration.ChunkSize} CZK"); + + //Serializer.SendEmail("Accumulation Bot - Buy", "You just spent " + schedule.FiatChunk + " CZK on " + schedule.Currency + ".", configuration.userId, forceEmail); + } + else + { + //sb_actions[schedule.Currency].Append("Not enough money to spend " + schedule.FiatChunk + " CZK on " + schedule.Currency + ".").Append("\r\n"); + //Serializer.SendEmail("Accumulation Bot - No Money", "Not enough money to spend " + schedule.FiatChunk + " CZK on " + schedule.Currency + ".", configuration.userId, forceEmail); + //MessageBox.Show("No Money!"); + await SendMessageAsync($"Not enough money ({CZK} CZK)", MessageTypeEnum.Warning); + return; + } + + + var afterBalance = coinmateAPIs[BotConfiguration.Currency].getBalances(); + double CZKafterBuy = afterBalance.Where(x => x.currency == "CZK").Sum(x => x.available); + + Dictionary fees = new Dictionary(); + + if(BotConfiguration.Currency == "BTC") + { + fees["BTC"] = coinmateAPIs["BTC"].getBTCWidthrawalFee(); + } + + fees["LTC"] = 0.0004; + fees["ETH"] = 0.01; + fees["DSH"] = 0.00001; + + + var price = coinmateAPIs[BotConfiguration.Currency].getActualExchangeRate().Item1; + + + double available = afterBalance.Where(x => x.currency == BotConfiguration.Currency).Sum(x => x.available); + double init = initBalance.Where(x => x.currency == BotConfiguration.Currency).Sum(x => x.available); + + double fee_cost = (fees[BotConfiguration.Currency] / available); + + sbInformationMessage.Append("Accumulation: " + (available - init).ToString("N8") + " " + BotConfiguration.Currency + " for " + BotConfiguration.ChunkSize.ToString("N2") + " CZK @ " + (BotConfiguration.ChunkSize / (available - init)).ToString("N2") + " CZK").Append("\r\n"); + + //Send them home + if (fee_cost <= BotConfiguration.MaxWithdrawalPercentageFee && BotConfiguration.WithdrawalEnabled && BotConfiguration.WithdrawalAddress != null) + { + coinmateAPIs[BotConfiguration.Currency].withdraw(available, BotConfiguration.WithdrawalAddress); + + sbInformationMessage.Append("Withdrawal: " + available.ToString("N8") + " " + BotConfiguration.Currency + " to " + BotConfiguration.WithdrawalAddress + " with " + (fee_cost * 100).ToString("N2") + " % fee").Append("\r\n"); + //Serializer.SendEmail("Accumulation Bot - Widthraw", "Widthraw of " + + " " + increase_string + " to " + schedule.WidthrawalAddress + ".", configuration.userId, forceEmail); + } + else + { + List reason = new List(); + if (fee_cost > BotConfiguration.MaxWithdrawalPercentageFee) + reason.Add("Limit exceeded"); + if (BotConfiguration.WithdrawalAddress == null) + reason.Add("No address"); + if (!BotConfiguration.WithdrawalEnabled) + reason.Add("Turned off"); + + sbInformationMessage.Append("Withdrawal: Denied - [" + String.Join(", ", reason) + "] - fee cost " + (fee_cost * 100).ToString("N2") + " %, limit " + (BotConfiguration.MaxWithdrawalPercentageFee * 100).ToString("N2") + " %").Append("\r\n"); + //Serializer.SendEmail("Accumulation Bot - Balance", "Current balance of " + schedule.Currency + " is " + available.ToString("N8") + " " + increase_string + ", fee cost " + (fee_cost * 100).ToString("N2") + "% above limit " + (schedule.WidthrawalFeeLimit * 100).ToString("N2") + "%).", configuration.userId, forceEmail); + } + + _cosmosDbContext = new CosmosDbContext(); + + var accumulationSummary = await _cosmosDbContext.GetAccumulationSummary(BotConfiguration.Currency); + + accumulationSummary.Buys += 1; + accumulationSummary.InvestedFiatAmount += (CZK - CZKafterBuy); + accumulationSummary.AccumulatedCryptoAmount += (available - init); + + await _cosmosDbContext.UpdateItemAsync(accumulationSummary); + + var profit = ((accumulationSummary.AccumulatedCryptoAmount * price) / accumulationSummary.InvestedFiatAmount) - 1; + + StringBuilder sb = new StringBuilder(); + sb.Append("🛒 [ACTIONS]").Append("\r\n"); + sb.Append(sbInformationMessage.ToString()); + sb.Append("").Append("\r\n"); + sb.Append("ℹ️ [SUMMARY]").Append("\r\n"); + sb.Append("Total accumulation: " + accumulationSummary.AccumulatedCryptoAmount.ToString("N8") + " " + BotConfiguration.Currency + " (" + accumulationSummary.InvestedFiatAmount.ToString("N2") + " CZK)").Append("\r\n"); + sb.Append("Avg Accumulated Price: " + (accumulationSummary.InvestedFiatAmount/accumulationSummary.AccumulatedCryptoAmount).ToString("N2") + " CZK/" + BotConfiguration.Currency).Append("\r\n"); + sb.Append("Current Price: " + price.ToString("N2") + " CZK/" + BotConfiguration.Currency).Append("\r\n"); + sb.Append("Current Profit: " + (profit * 100).ToString("N2") + " % (" + (profit * accumulationSummary.InvestedFiatAmount).ToString("N2") + " CZK)").Append("\r\n"); + //sb.Append("Zero-out the profit: " + ((profit >= 0) ? ("Sell " + (profit * totals[schedule.Currency].Item1).ToString("N8") + " " + schedule.Currency + " (" + (profit * totals[schedule.Currency].Item2).ToString("N2") + " CZK)") : "You are at loss, don't sell")).Append("\r\n"); + + //sb.Append("Next accumulation: " + schedule.NextExecutedAccumulation.ToString("dd-MM-yyy HH:mm:ss")).Append("\r\n"); + sb.Append("Fiat balance: " + CZKafterBuy.ToString("N2") + " CZK").Append("\r\n"); + sb.Append("Current balance: " + afterBalance.Where(x => x.currency == BotConfiguration.Currency).Sum(x => x.available).ToString("N8") + " " + BotConfiguration.Currency).Append("\r\n"); + //sb.Append("Fiat depletion: " + Simulate(CZK, schedule).ToString("dd-MM-yyy HH:00:00")).Append("\r\n"); + await SendMessageAsync(sb.ToString()); + } + catch (Exception ex) + { + await SendMessageAsync(ex.ToString(), MessageTypeEnum.Error); + return; + } + + } + + private void inicializeAPI(string crypto) + { + this.coinmateAPIs = new Dictionary(); + this.coinmateAPIs[crypto] = new CoinmateAPI($"{crypto}_CZK", BotConfiguration.CoinMateCredentials, Log); + } + + public async Task SendMessageAsync(string message, MessageTypeEnum messageType = MessageTypeEnum.Information, int attempt = 0) + { + switch (messageType) + { + case MessageTypeEnum.Information: + Log.LogInformation(message); + message = $"#AccuBot - #{BotConfiguration.Currency} - #{BotConfiguration.UserName}{Environment.NewLine}{message}"; + break; + case MessageTypeEnum.Warning: + Log.LogWarning(message); + message = $"⚠️ #AccuBot - #{BotConfiguration.Currency} - #{BotConfiguration.UserName}{Environment.NewLine}{message}"; + break; + case MessageTypeEnum.Error: + Log.LogError(message); + message = $"❌ #AccuBot - #{BotConfiguration.Currency} - #{BotConfiguration.UserName}{Environment.NewLine}{message}"; + break; + } + + try + { + await bot.SendTextMessageAsync(BotConfiguration.TelegramChannel, message, Telegram.Bot.Types.Enums.ParseMode.Html); + } + catch (Exception e) + { + if (attempt >= 2) + { + Log.LogError(e, "SendTextMessageAsync error"); + return e.ToString(); + } + //Repeat if exception (i.e. too many requests) occured + Thread.Sleep(300); + return await SendMessageAsync(message, MessageTypeEnum.Error , ++attempt); + } + + return null; + } + } +} diff --git a/CryptoBotCore/CosmosDB/CosmosDbContext.cs b/CryptoBotCore/CosmosDB/CosmosDbContext.cs new file mode 100644 index 0000000..b781ee7 --- /dev/null +++ b/CryptoBotCore/CosmosDB/CosmosDbContext.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Azure.Cosmos; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using CryptoBotCore.CosmosDB.Model; +using CryptoBotCore.Models; + +namespace CryptoBotCore.CosmosDB +{ + public class CosmosDbContext + { + // The Cosmos client instance + private CosmosClient cosmosClient; + + private Database database; + + private Container container; + + private string databaseId = "AccBotDatabase"; + private string containerId = "AccBotContainer"; + + public CosmosDbContext() + { + this.cosmosClient = new CosmosClient(BotConfiguration.CosmosDbEndpointUri, BotConfiguration.CosmosDbPrimaryKey); + CreateDatabaseAsync().GetAwaiter().GetResult(); + CreateContainerAsync().GetAwaiter().GetResult(); + } + + + /// + /// Create the database if it does not exist + /// + private async Task CreateDatabaseAsync() + { + // Create a new database + this.database = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(databaseId); + Console.WriteLine("Created Database: {0}\n", this.database.Id); + } + + /// + /// Create the container if it does not exist. + /// Specifiy "/LastName" as the partition key since we're storing family information, to ensure good distribution of requests and storage. + /// + /// + private async Task CreateContainerAsync() + { + // Create a new container + this.container = await this.database.CreateContainerIfNotExistsAsync(containerId, "/CryptoName"); + Console.WriteLine("Created Container: {0}\n", this.container.Id); + } + + public async Task AddItemAsync(AccumulationSummary item) + { + await this.container.CreateItemAsync(item, new PartitionKey(item.CryptoName.ToString())); + } + + public async Task DeleteItemAsync(string id, string cryptoName) + { + await this.container.DeleteItemAsync(id, new PartitionKey(cryptoName)); + } + + public async Task GetItemAsync(string id) + { + try + { + ItemResponse response = await this.container.ReadItemAsync(id, new PartitionKey(id)); + return response.Resource; + } + catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return null; + } + + } + + public async Task> GetItemsAsync(string queryString) + { + var query = this.container.GetItemQueryIterator(new QueryDefinition(queryString)); + List results = new List(); + while (query.HasMoreResults) + { + var response = await query.ReadNextAsync(); + + results.AddRange(response.ToList()); + } + + return results; + } + + private async Task GetAccumulationSummaryQuery(string CryptoName) + { + var query = this.container.GetItemQueryIterator(new QueryDefinition($"select top 1 * from c where c.CryptoName = '{CryptoName}'")); + List results = new List(); + while (query.HasMoreResults) + { + var response = await query.ReadNextAsync(); + + results.AddRange(response.ToList()); + } + + return results.FirstOrDefault(); + } + + public async Task GetAccumulationSummary(string CryptoName) + { + var summary = await GetAccumulationSummaryQuery(CryptoName); + + if (summary == null) + { + AccumulationSummary accumulationSummary = new AccumulationSummary() + { + AccumulatedCryptoAmount = 0, + CryptoName = CryptoName, + Buys = 0, + Id = Guid.NewGuid(), + InvestedFiatAmount = 0 + }; + ItemResponse accumulationSummaryResponse = await this.container.CreateItemAsync(accumulationSummary, new PartitionKey(accumulationSummary.CryptoName)); + Console.WriteLine("Created item in database with id: {0} Operation consumed {1} RUs.\n", accumulationSummaryResponse.Resource.Id, accumulationSummaryResponse.RequestCharge); + summary = await GetAccumulationSummaryQuery(CryptoName); + } + + return summary; + } + + + + public async Task UpdateItemAsync(AccumulationSummary item) + { + await this.container.UpsertItemAsync(item, new PartitionKey(item.CryptoName.ToString())); + } + } +} diff --git a/CryptoBotCore/CosmosDB/Model/AccumulationSummary.cs b/CryptoBotCore/CosmosDB/Model/AccumulationSummary.cs new file mode 100644 index 0000000..e4e37cc --- /dev/null +++ b/CryptoBotCore/CosmosDB/Model/AccumulationSummary.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.CosmosDB.Model +{ + public class AccumulationSummary + { + [JsonProperty(PropertyName = "id")] + public Guid Id { get; set; } + public string CryptoName { get; set; } + public double AccumulatedCryptoAmount { get; set; } + public double InvestedFiatAmount { get; set; } + public int Buys { get; set; } + } +} diff --git a/CryptoBotCore/CryptoBotCore.csproj b/CryptoBotCore/CryptoBotCore.csproj new file mode 100644 index 0000000..0cb20a6 --- /dev/null +++ b/CryptoBotCore/CryptoBotCore.csproj @@ -0,0 +1,27 @@ + + + + Exe + netcoreapp3.1 + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/CryptoBotCore/Models/BotConfiguration.cs b/CryptoBotCore/Models/BotConfiguration.cs new file mode 100644 index 0000000..ec25c6e --- /dev/null +++ b/CryptoBotCore/Models/BotConfiguration.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.Models +{ + public static class BotConfiguration + { + public static string TelegramChannel { get; set; } + public static string TelegramBot { get; set; } + public static string Currency { get; set; } + public static CoinMateCredentials CoinMateCredentials { get; set; } + public static int ChunkSize { get; set; } + public static double MaxWithdrawalPercentageFee { get; set; } = 0.001; + public static string WithdrawalAddress { get; set; } + public static bool WithdrawalEnabled { get; set; } + public static string UserName { get; set; } + public static string CosmosDbEndpointUri { get; set; } + public static string CosmosDbPrimaryKey { get; set; } +} +} diff --git a/CryptoBotCore/Models/CoinMateCredentials.cs b/CryptoBotCore/Models/CoinMateCredentials.cs new file mode 100644 index 0000000..6af56fb --- /dev/null +++ b/CryptoBotCore/Models/CoinMateCredentials.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.Models +{ + public class CoinMateCredentials + { + public int ClientId { get; set; } + public string PublicKey { get; set; } + public string PrivateKey { get; set; } + + + //public CoinMateCredentials(IConfiguration configuration) + //{ + // ClientId = Int32.Parse(configuration["CoinMate:API_ClientId"]); + // PrivateKey = configuration["CoinMate:API_PrivateKey"]; + // PublicKey = configuration["CoinMate:API_PublicKey"]; + //} + } +} diff --git a/CryptoBotCore/Models/Credit.cs b/CryptoBotCore/Models/Credit.cs new file mode 100644 index 0000000..462c0ba --- /dev/null +++ b/CryptoBotCore/Models/Credit.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CryptoBotCore.Models +{ + [Serializable] + public class Credit + { + public int id { get; set; } + public string currency { get; set; } + public string status { get; set; } + public string rate { get; set; } + public int period { get; set; } + public double amount { get; set; } + public string timestamp { get; set; } + } +} diff --git a/CryptoBotCore/Models/Currency.cs b/CryptoBotCore/Models/Currency.cs new file mode 100644 index 0000000..5df8789 --- /dev/null +++ b/CryptoBotCore/Models/Currency.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.Models +{ + public class Currency + { + public string CURRE_Key { get; set; } + public string CURRE_Description { get; set; } + } +} diff --git a/CryptoBotCore/Models/GenericRequest.cs b/CryptoBotCore/Models/GenericRequest.cs new file mode 100644 index 0000000..2935269 --- /dev/null +++ b/CryptoBotCore/Models/GenericRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.Models +{ + public class GenericRequest + { + public string request; + public string nonce; + public ArrayList options = new ArrayList(); + } +} diff --git a/CryptoBotCore/Models/MarketPriceSnapshot.cs b/CryptoBotCore/Models/MarketPriceSnapshot.cs new file mode 100644 index 0000000..c884104 --- /dev/null +++ b/CryptoBotCore/Models/MarketPriceSnapshot.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.Models +{ + using System; + using System.Collections.Generic; + + public class MarketPriceSnapshot + { + public System.DateTime MARPS_DateTime { get; set; } + public System.Guid MARPS_ApplicationId { get; set; } + public string MARPS_PLATF_Platform_Key { get; set; } + public string MARPS_CURRE_CurrencyFrom_Key { get; set; } + public string MARPS_CURRE_CurrencyTo_Key { get; set; } + public Nullable MARPS_CurrencyFrom_Buy { get; set; } + public Nullable MARPS_CurrencyFrom_Sell { get; set; } + public Nullable MARPS_CurrencyCZK_Buy { get; set; } + public Nullable MARPS_CurrencyCZK_Sell { get; set; } + public Nullable MARPS_CurrencyCZK_ExchangeRate { get; set; } + public int MARPS_Id { get; set; } + + public virtual Currency Currency { get; set; } + public virtual Currency Currency1 { get; set; } + public virtual Plaftorm Plaftorm { get; set; } + } + } \ No newline at end of file diff --git a/CryptoBotCore/Models/MessageTypeEnum.cs b/CryptoBotCore/Models/MessageTypeEnum.cs new file mode 100644 index 0000000..a1f7390 --- /dev/null +++ b/CryptoBotCore/Models/MessageTypeEnum.cs @@ -0,0 +1,10 @@ +namespace CryptoBotCore.Models +{ + public enum MessageTypeEnum + { + Information, + Warning, + Error + + } +} \ No newline at end of file diff --git a/CryptoBotCore/Models/NewOrderRequest.cs b/CryptoBotCore/Models/NewOrderRequest.cs new file mode 100644 index 0000000..3b73b24 --- /dev/null +++ b/CryptoBotCore/Models/NewOrderRequest.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace CryptoBotCore.Models +{ + public enum OrderType + { + MarginMarket, + MarginLimit, + MarginStop, + MarginTrailingStop, + ExchangeMarket, + ExchangeLimit, + ExchangeStop, + ExchangeTrailingStop + } + public enum OrderSide + { + Buy, + Sell + } + public enum OrderExchange + { + Bitfinex, + Bitstamp, + All + } + + public class NewOrderRequest : GenericRequest + { + public string symbol; + public string amount; + public string price; + public string exchange; + public string side; + public string type; + //public bool is_hidden=false; + public NewOrderRequest(string nonce, string symbol, Double amount, Double price, OrderExchange exchange, OrderSide side, OrderType type) + { + this.symbol = symbol; + this.amount = amount.ToString(CultureInfo.InvariantCulture); + this.price = price.ToString(CultureInfo.InvariantCulture); + this.exchange = EnumHelper.EnumToStr(exchange); + this.side = EnumHelper.EnumToStr(side); + this.type = EnumHelper.EnumToStr(type); + this.nonce = nonce; + this.request = "/v1/order/new"; + } + } + + public class NewOrderResponse : OrderStatusResponse + { + public string order_id; + + public static NewOrderResponse FromJSON(string response) + { + NewOrderResponse resp = JsonSerializer.Deserialize(response); + return resp; + } + } + public class EnumHelper + { + private static Dictionary enumStr = null; + private static Dictionary Get() + { + if (enumStr == null) + { + enumStr = new Dictionary(); + enumStr.Add(OrderExchange.All, "all"); + enumStr.Add(OrderExchange.Bitfinex, "bitfinex"); + enumStr.Add(OrderExchange.Bitstamp, "bitstamp"); + + enumStr.Add(OrderSide.Buy, "buy"); + enumStr.Add(OrderSide.Sell, "sell"); + + enumStr.Add(OrderType.MarginLimit, "limit"); + enumStr.Add(OrderType.MarginMarket, "market"); + enumStr.Add(OrderType.MarginStop, "stop"); + enumStr.Add(OrderType.MarginTrailingStop, "trailing-stop"); + enumStr.Add(OrderType.ExchangeLimit, "exchange limit"); + enumStr.Add(OrderType.ExchangeMarket, "exchange market"); + enumStr.Add(OrderType.ExchangeStop, "exchange stop"); + enumStr.Add(OrderType.ExchangeTrailingStop, "exchange trailing-stop"); + } + return enumStr; + } + + public static string EnumToStr(object enumItem) + { + return Get()[enumItem]; + } + + } +} diff --git a/CryptoBotCore/Models/Offer.cs b/CryptoBotCore/Models/Offer.cs new file mode 100644 index 0000000..74fbf52 --- /dev/null +++ b/CryptoBotCore/Models/Offer.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace CryptoBotCore.Models +{ + [Serializable] + public class Offer + { + public int id { get; set; } + public string currency { get; set; } + public string rate { get; set; } + public int period { get; set; } + public string direction { get; set; } + public string timestamp { get; set; } + public bool is_live { get; set; } + public bool is_cancelled { get; set; } + public string original_amount { get; set; } + public string remaining_amount { get; set; } + public string executed_amount { get; set; } + } + + public class CancelOfferRequest : GenericRequest + { + public long offer_id; + public CancelOfferRequest(string nonce, long offer_id) + { + this.nonce = nonce; + this.offer_id = offer_id; + this.request = "/v1/offer/cancel"; + } + } + + public class OfferRequest : GenericRequest + { + public string amount; + public string currency; + public string rate; + public int period; + public string direction; + + public OfferRequest(string nonce, double amount, string currency, double rate, int period) + { + this.amount = amount.ToString(); + this.currency = currency; + this.rate = rate.ToString(); + this.period = period; + this.direction = "lend"; + this.nonce = nonce; + this.request = "/v1/offer/new"; + } + } + + public class OfferResponce + { + public Offer offer; + + public static OfferResponce FromJSON(string response) + { + Offer offer = JsonSerializer.Deserialize(response); + return new OfferResponce(offer); + } + + private OfferResponce(Offer offer) + { + this.offer = offer; + } + } +} diff --git a/CryptoBotCore/Models/OpenOrder.cs b/CryptoBotCore/Models/OpenOrder.cs new file mode 100644 index 0000000..a0df06c --- /dev/null +++ b/CryptoBotCore/Models/OpenOrder.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CryptoBotCore.Models +{ + [Serializable] + public class OpenOrder + { + public string id { get; set; } + public Double timestamp { get; set; } + public string type { get; set; } + public double price { get; set; } + public double amount { get; set; } + public long orderOrphanTTL { get; set; } + + public Double packageSize { get; set; } + + public string market { get; set; } + public string currencyPair { get; set; } + + /// + /// Is it already in the eschange market? Default is true + /// + public bool isActive { get; set; } = true; + + public Double? buyFee { get; set; } + + public string feeCurrency { get; set; } + + public Double? executed_amount { get; set; } + public Double? remaining_amount { get; set; } + public Double? original_amount { get; set; } + + public OpenOrder buyOrder { get; set; } + + public string pair { get; set; } + + public OpenOrder() { } + + public OpenOrder(string id, string type, double price, double amount, Double timestamp, Double packageSize, string market = "", string pair = "" + , Double? executed_amount = null, Double? remaining_amount = null, Double? original_amount = null) + { + this.id = id; + this.type = type; + this.price = price; + this.amount = amount; + this.timestamp = timestamp; + this.orderOrphanTTL = 0; + this.market = market; + this.pair = pair; + this.packageSize = packageSize; + this.executed_amount = executed_amount; + this.remaining_amount = remaining_amount; + this.original_amount = original_amount; + this.isActive = true; + } + + public string ToString(string pair) + { + string baseCurr = pair.Substring(3, 3).ToUpper(); + string cryptoCurr = pair.Substring(0, 3).ToUpper(); + + return String.Format("{0, 15} {1, 3} @ {2,15} {3,3} = {4, 9} {5,3} in {6,19}", Math.Round(amount, 8).ToString("N8"), cryptoCurr, Math.Round(price, 5).ToString("N5"), baseCurr, Math.Round(amount * price, 2).ToString("N2"), baseCurr, Utility.getDateTimeFromUnixTimestamp(timestamp).ToString("yyyy-MM-dd HH:mm:ss")); + } + + public string ToString(string baseCurrency, string quoteCurrency) + { + string baseCurr = baseCurrency.ToUpper(); + string cryptoCurr = quoteCurrency.ToUpper(); + + return String.Format("{0, 15} {1, 3} @ {2,15} {3,3} = {4, 9} {5,3} in {6,19}", Math.Round(amount, 8).ToString("N8"), cryptoCurr, Math.Round(price, 5).ToString("N5"), baseCurr, Math.Round(amount * price, 2).ToString("N2"), baseCurr, Utility.getDateTimeFromUnixTimestamp(timestamp).ToString("yyyy-MM-dd HH:mm:ss")); + } + } +} diff --git a/CryptoBotCore/Models/OrderStatusResponse.cs b/CryptoBotCore/Models/OrderStatusResponse.cs new file mode 100644 index 0000000..8a5da4f --- /dev/null +++ b/CryptoBotCore/Models/OrderStatusResponse.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace CryptoBotCore.Models +{ + public class OrderStatusResponse + { + public string id; + public string symbol; + public string exchange; + public string price; + public string avg_execution_price; + public string type; + public string timestamp; + public string is_live; + public string is_cancelled; + public string was_forced; + public string executed_amount; + public string remaining_amount; + public string original_amount; + public string side; + + public static OrderStatusResponse FromJSON(string response) + { + return JsonSerializer.Deserialize(response); + } + } + + public class CancelOrderRequest : GenericRequest + { + public long order_id; + public CancelOrderRequest(string nonce, long order_id) + { + this.nonce = nonce; + this.order_id = order_id; + this.request = "/v1/order/cancel"; + } + } + + public class ActiveOrdersRequest : GenericRequest + { + public ActiveOrdersRequest(string nonce) + { + this.nonce = nonce; + this.request = "/v1/orders"; + } + } + + public class CancelOrderResponse : OrderStatusResponse + { + public static CancelOrderResponse FromJSON(string response) + { + return JsonSerializer.Deserialize(response); + } + } + + public class ActiveOrdersResponse + { + public List orders; + + public static ActiveOrdersResponse FromJSON(string response) + { + List orders = JsonSerializer.Deserialize>(response); + return new ActiveOrdersResponse(orders); + } + private ActiveOrdersResponse(List orders) + { + this.orders = orders; + } + } +} diff --git a/CryptoBotCore/Models/Platform.cs b/CryptoBotCore/Models/Platform.cs new file mode 100644 index 0000000..a1ff79d --- /dev/null +++ b/CryptoBotCore/Models/Platform.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoBotCore.Models +{ + public class Plaftorm + { + + public string PLATF_Key { get; set; } + public string PLATF_Desciption { get; set; } + + } +} diff --git a/CryptoBotCore/Program.cs b/CryptoBotCore/Program.cs new file mode 100644 index 0000000..aad70fa --- /dev/null +++ b/CryptoBotCore/Program.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System; + +namespace CryptoBotCore +{ + class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.AddJsonFile("AppSettings.json", + optional: true, + reloadOnChange: true); + }) + .ConfigureServices((hostContext, services) => + { + services.AddHostedService(); + }); + } +} diff --git a/CryptoBotCore/Utility.cs b/CryptoBotCore/Utility.cs new file mode 100644 index 0000000..ea38ba2 --- /dev/null +++ b/CryptoBotCore/Utility.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CryptoBotCore +{ + public static class Utility + { + public static Double timeDifference(Double dateFrom, Double dateTo) + { + return dateTo - dateFrom; + } + + public static Double getUnixTimestamps(DateTime time) + { + return (time.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc))).TotalSeconds; + } + + public static DateTime getDateTimeFromUnixTimestamp(Double unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); + if(unixTimeStamp > 1000000000000) + dtDateTime = dtDateTime.AddMilliseconds(unixTimeStamp).ToLocalTime(); + else + dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime(); + return dtDateTime; + } + + public static DateTime StartOfWeek(this DateTime dt, DayOfWeek startOfWeek = DayOfWeek.Monday) + { + int diff = (7 + (dt.DayOfWeek - startOfWeek)) % 7; + return dt.AddDays(-1 * diff).Date; + } + } +} diff --git a/CryptoBotFunction/.gitignore b/CryptoBotFunction/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/CryptoBotFunction/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# 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 + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_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 + +# 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 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 \ No newline at end of file diff --git a/CryptoBotFunction/AccumulationBotFunction.cs b/CryptoBotFunction/AccumulationBotFunction.cs new file mode 100644 index 0000000..4c19a3a --- /dev/null +++ b/CryptoBotFunction/AccumulationBotFunction.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using CryptoBotCore.BotStrategies; +using CryptoBotCore.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Azure.WebJobs.Host; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CryptoBotFunction +{ + public static class AccumulationBotFunction + { + [FunctionName("AccumulationBotFunction")] + public static void Run([TimerTrigger("0 0 */4 * * *")] TimerInfo myTimer, ILogger log) + { + LoadAppSettings(); + + var accumulationBot = new AccumulationBot(log); + accumulationBot.Tick().GetAwaiter().GetResult(); + } + + private static void LoadAppSettings() + { + var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + var builder = new ConfigurationBuilder() + .AddJsonFile($"appsettings.json", true, true) + .AddJsonFile($"appsettings.{env}.json", true, true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + BotConfiguration.Currency = config["Currency"]; + BotConfiguration.UserName = config["Name"]; + BotConfiguration.ChunkSize = Int32.Parse(config["ChunkSize"]); + BotConfiguration.WithdrawalEnabled = bool.Parse(config["WithdrawalEnabled"]); + BotConfiguration.WithdrawalAddress = config["WithdrawalAddress"]; + BotConfiguration.TelegramChannel = config["TelegramChannel"]; + BotConfiguration.TelegramBot = config["TelegramBot"]; + BotConfiguration.CosmosDbEndpointUri = config["CosmosDbEndpointUri"]; + BotConfiguration.CosmosDbPrimaryKey = config["CosmosDbPrimaryKey"]; + BotConfiguration.CoinMateCredentials = new CoinMateCredentials(); + BotConfiguration.CoinMateCredentials.ClientId = Int32.Parse(config["CoinMateCredentials_ClientId"]); + BotConfiguration.CoinMateCredentials.PublicKey = config["CoinMateCredentials_PublicKey"]; + BotConfiguration.CoinMateCredentials.PrivateKey = config["CoinMateCredentials_PrivateKey"]; + } + } +} \ No newline at end of file diff --git a/CryptoBotFunction/CryptoBotFunction.csproj b/CryptoBotFunction/CryptoBotFunction.csproj new file mode 100644 index 0000000..431ec7d --- /dev/null +++ b/CryptoBotFunction/CryptoBotFunction.csproj @@ -0,0 +1,21 @@ + + + netcoreapp3.1 + v3 + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + diff --git a/CryptoBotFunction/Properties/ServiceDependencies/CrBts - Zip Deploy/profile.arm.json b/CryptoBotFunction/Properties/ServiceDependencies/CrBts - Zip Deploy/profile.arm.json new file mode 100644 index 0000000..9b2c385 --- /dev/null +++ b/CryptoBotFunction/Properties/ServiceDependencies/CrBts - Zip Deploy/profile.arm.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_dependencyType": "function.windows.appService" + }, + "parameters": { + "resourceGroupName": { + "type": "string", + "defaultValue": "CBot", + "metadata": { + "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." + } + }, + "resourceGroupLocation": { + "type": "string", + "defaultValue": "germanywestcentral", + "metadata": { + "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." + } + }, + "resourceName": { + "type": "string", + "defaultValue": "CrBts", + "metadata": { + "description": "Name of the main resource to be created by this template." + } + }, + "resourceLocation": { + "type": "string", + "defaultValue": "[parameters('resourceGroupLocation')]", + "metadata": { + "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." + } + } + }, + "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('resourceGroupLocation')]", + "apiVersion": "2019-10-01" + }, + { + "type": "Microsoft.Resources/deployments", + "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "apiVersion": "2019-10-01", + "dependsOn": [ + "[parameters('resourceGroupName')]" + ], + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "resourceGroupName": { + "value": "[parameters('resourceGroupName')]" + }, + "resourceGroupLocation": { + "value": "[parameters('resourceGroupLocation')]" + }, + "resourceName": { + "value": "[parameters('resourceName')]" + }, + "resourceLocation": { + "value": "[parameters('resourceLocation')]" + } + }, + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceGroupName": { + "type": "string" + }, + "resourceGroupLocation": { + "type": "string" + }, + "resourceName": { + "type": "string" + }, + "resourceLocation": { + "type": "string" + } + }, + "variables": { + "storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]", + "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", + "storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]", + "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]", + "function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]" + }, + "resources": [ + { + "location": "[parameters('resourceLocation')]", + "name": "[parameters('resourceName')]", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "tags": { + "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" + }, + "dependsOn": [ + "[variables('appServicePlan_ResourceId')]", + "[variables('storage_ResourceId')]" + ], + "kind": "functionapp", + "properties": { + "name": "[parameters('resourceName')]", + "kind": "functionapp", + "httpsOnly": true, + "reserved": false, + "serverFarmId": "[variables('appServicePlan_ResourceId')]", + "siteConfig": { + "alwaysOn": true + } + }, + "identity": { + "type": "SystemAssigned" + }, + "resources": [ + { + "name": "appsettings", + "type": "config", + "apiVersion": "2015-08-01", + "dependsOn": [ + "[variables('function_ResourceId')]" + ], + "properties": { + "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]", + "FUNCTIONS_EXTENSION_VERSION": "~3", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + } + } + ] + }, + { + "location": "[parameters('resourceGroupLocation')]", + "name": "[variables('storage_name')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2017-10-01", + "tags": { + "[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty" + }, + "properties": { + "supportsHttpsTrafficOnly": true + }, + "sku": { + "name": "Standard_LRS" + }, + "kind": "Storage" + }, + { + "location": "[parameters('resourceGroupLocation')]", + "name": "[variables('appServicePlan_name')]", + "type": "Microsoft.Web/serverFarms", + "apiVersion": "2015-08-01", + "sku": { + "name": "S1", + "tier": "Standard", + "family": "S", + "size": "S1" + }, + "properties": { + "name": "[variables('appServicePlan_name')]" + } + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/CryptoBotFunction/Properties/serviceDependencies.json b/CryptoBotFunction/Properties/serviceDependencies.json new file mode 100644 index 0000000..c264e8c --- /dev/null +++ b/CryptoBotFunction/Properties/serviceDependencies.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + } + } +} \ No newline at end of file diff --git a/CryptoBotFunction/Properties/serviceDependencies.local.json b/CryptoBotFunction/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..5a956e8 --- /dev/null +++ b/CryptoBotFunction/Properties/serviceDependencies.local.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + } + } +} \ No newline at end of file diff --git a/CryptoBotFunction/host.json b/CryptoBotFunction/host.json new file mode 100644 index 0000000..beb2e40 --- /dev/null +++ b/CryptoBotFunction/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + } + } + } +} \ No newline at end of file