mirror of
https://github.com/vdemydiuk/mtapi.git
synced 2026-07-27 18:47:55 +00:00
Issue #25: Implemented function CopyTicks. Used json request
This commit is contained in:
Executable
+9
@@ -0,0 +1,9 @@
|
||||
namespace MtApi5
|
||||
{
|
||||
public enum CopyTicksFlag
|
||||
{
|
||||
Info = 1, // ticks with Bid and/or Ask changes
|
||||
Trade = 2, // ticks with changes in Last and Volume
|
||||
All = -1 // all ticks
|
||||
}
|
||||
}
|
||||
Executable
+188
@@ -0,0 +1,188 @@
|
||||
namespace MtApi5
|
||||
{
|
||||
//Error codes from https://www.mql5.com/en/docs/constants/errorswarnings/errorcodes
|
||||
|
||||
public enum ErrorCode
|
||||
{
|
||||
ErrCustom = -1, // Error occurred in MtApi5.
|
||||
ErrSuccess = 0, // The operation completed successfully
|
||||
ErrInternalError = 4001, // Unexpected internal error
|
||||
ErrWrongInternalParameter = 4002, // Wrong parameter in the inner call of the client terminal function
|
||||
ErrInvalidParameter = 4003, // Wrong parameter when calling the system function
|
||||
ErrNotEnoughMemory = 4004, // Not enough memory to perform the system function
|
||||
ErrStructWithobjectsOrclass = 4005, // The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes
|
||||
ErrInvalidArray = 4006, // Array of a wrong type, wrong size, or a damaged object of a dynamic array
|
||||
ErrArrayResizeError = 4007, // Not enough memory for the relocation of an array, or an attempt to change the size of a static array
|
||||
ErrStringResizeError = 4008, // Not enough memory for the relocation of string
|
||||
ErrNotinitializedString = 4009, // Not initialized string
|
||||
ErrInvalidDatetime = 4010, // Invalid date and/or time
|
||||
ErrArrayBadSize = 4011, // Requested array size exceeds 2 GB
|
||||
ErrInvalidPointer = 4012, // Wrong pointer
|
||||
ErrInvalidPointerType = 4013, // Wrong type of pointer
|
||||
ErrFunctionNotAllowed = 4014, // Function is not allowed for call
|
||||
ErrResourceNameDuplicated = 4015, // The names of the dynamic and the static resource match
|
||||
ErrResourceNotFound = 4016, // Resource with this name has not been found in EX5
|
||||
ErrResourceUnsuppotedType = 4017, // Unsupported resource type or its size exceeds 16 Mb
|
||||
ErrResourceNameIsTooLong = 4018, // The resource name exceeds 63 characters
|
||||
|
||||
ErrChartWrongId = 4101, // Wrong chart ID
|
||||
ErrChartNoReply = 4102, // Chart does not respond
|
||||
ErrChartNotFound = 4103, // Chart not found
|
||||
ErrChartNoExpert = 4104, // No Expert Advisor in the chart that could handle the event
|
||||
ErrChartCannotOpen = 4105, // Chart opening error
|
||||
ErrChartCannotChange = 4106, // Failed to change chart symbol and period
|
||||
ErrChartWrongParameter = 4107, // Error value of the parameter for the function of working with charts
|
||||
ErrChartCannotCreateTimer = 4108, // Failed to create timer
|
||||
ErrChartWrongProperty = 4109, // Wrong chart property ID
|
||||
ErrChartScreenshotFailed = 4110, // Error creating screenshots
|
||||
ErrChartNavigateFailed = 4111, // Error navigating through chart
|
||||
ErrChartTemplateFailed = 4112, // Error applying template
|
||||
ErrChartWindowNotFound = 4113, // Subwindow containing the indicator was not found
|
||||
ErrChartIndicatorCannotAdd = 4114, // Error adding an indicator to chart
|
||||
ErrChartIndicatorCannotDel = 4115, // Error deleting an indicator from the chart
|
||||
ErrChartIndicatorNotFound = 4116, // Indicator not found on the specified chart
|
||||
|
||||
ErrObjectError = 4201, // Error working with a graphical object
|
||||
ErrObjectNotFound = 4202, // Graphical object was not found
|
||||
ErrObjectWrongProperty = 4203, // Wrong ID of a graphical object property
|
||||
ErrObjectGetdateFailed = 4204, // Unable to get date corresponding to the value
|
||||
ErrObjectGetvalueFailed = 4205, // Unable to get value corresponding to the date
|
||||
|
||||
ErrMarketUnknownSymbol = 4301, // Unknown symbol
|
||||
ErrMarketNotSelected = 4302, // Symbol is not selected in MarketWatch
|
||||
ErrMarketWrongProperty = 4303, // Wrong identifier of a symbol property
|
||||
ErrMarketLasttimeUnknown = 4304, // Time of the last tick is not known (no ticks)
|
||||
ErrMarketSelectError = 4305, // Error adding or deleting a symbol in MarketWatch
|
||||
|
||||
ErrHistoryNotFound = 4401, // Requested history not found
|
||||
ErrHistoryWrongProperty = 4402, // Wrong ID of the history property
|
||||
|
||||
ErrGlobalvariableNotFound = 4501, // Global variable of the client terminal is not found
|
||||
ErrGlobalvariableExists = 4502, // Global variable of the client terminal with the same name already exists
|
||||
ErrMailSendFailed = 4510, // Email sending failed
|
||||
ErrPlaySoundFailed = 4511, // Sound playing failed
|
||||
ErrMql5WrongProperty = 4512, // Wrong identifier of the program property
|
||||
ErrTerminalWrongProperty = 4513, // Wrong identifier of the terminal property
|
||||
ErrFtpSendFailed = 4514, // File sending via ftp failed
|
||||
ErrNotificationSendFailed = 4515, // Failed to send a notification
|
||||
ErrNotificationWrongParameter = 4516, // Invalid parameter for sending a notification — an empty string or NULL has been passed to the SendNotification() function
|
||||
ErrNotificationWrongSettings = 4517, // Wrong settings of notifications in the terminal (ID is not specified or permission is not set)
|
||||
ErrNotificationTooFrequent = 4518, // Too frequent sending of notifications
|
||||
ErrFtpNoserver = 4519, // FTP server is not specified
|
||||
ErrFtpNologin = 4520, // FTP login is not specified
|
||||
ErrFtpFileError = 4521, // File not found in the MQL5\Files directory to send on FTP server
|
||||
ErrFtpConnectFailed = 4522, // FTP connection failed
|
||||
ErrFtpChangedir = 4523, // FTP path not found on server
|
||||
ErrFtpClosed = 4524, // FTP connection closed
|
||||
|
||||
ErrBuffersNoMemory = 4601, // Not enough memory for the distribution of indicator buffers
|
||||
ErrBuffersWrongIndex = 4602, // Wrong indicator buffer index
|
||||
|
||||
ErrCustomWrongProperty = 4603, // Wrong ID of the custom indicator property
|
||||
|
||||
ErrAccountWrongProperty = 4701, // Wrong account property ID
|
||||
ErrTradeWrongProperty = 4751, // Wrong trade property ID
|
||||
ErrTradeDisabled = 4752, // Trading by Expert Advisors prohibited
|
||||
ErrTradePositionNotFound = 4753, // Position not found
|
||||
ErrTradeOrderNotFound = 4754, // Order not found
|
||||
ErrTradeDealNotFound = 4755, // Deal not found
|
||||
ErrTradeSendFailed = 4756, // Trade request sending failed
|
||||
|
||||
ErrIndicatorUnknownSymbol = 4801, // Unknown symbol
|
||||
ErrIndicatorCannotCreate = 4802, // Indicator cannot be created
|
||||
ErrIndicatorNoMemory = 4803, // Not enough memory to add the indicator
|
||||
ErrIndicatorCannotApply = 4804, // The indicator cannot be applied to another indicator
|
||||
ErrIndicatorCannotAdd = 4805, // Error applying an indicator to chart
|
||||
ErrIndicatorDataNotFound = 4806, // Requested data not found
|
||||
ErrIndicatorWrongHandle = 4807, // Wrong indicator handle
|
||||
ErrIndicatorWrongParameters = 4808, // Wrong number of parameters when creating an indicator
|
||||
ErrIndicatorParametersMissing = 4809, // No parameters when creating an indicator
|
||||
ErrIndicatorCustomName = 4810, // The first parameter in the array must be the name of the custom indicator
|
||||
ErrIndicatorParameterType = 4811, // Invalid parameter type in the array when creating an indicator
|
||||
ErrIndicatorWrongIndex = 4812, // Wrong index of the requested indicator buffer
|
||||
|
||||
ErrBooksCannotAdd = 4901, // Depth Of Market can not be added
|
||||
ErrBooksCannotDelete = 4902, // Depth Of Market can not be removed
|
||||
ErrBooksCannotGet = 4903, // The data from Depth Of Market can not be obtained
|
||||
ErrBooksCannotSubscribe = 4904, // Error in subscribing to receive new data from Depth Of Market
|
||||
|
||||
ErrTooManyFiles = 5001, // More than 64 files cannot be opened at the same time
|
||||
ErrWrongFilename = 5002, // Invalid file name
|
||||
ErrTooLongFilename = 5003, // Too long file name
|
||||
ErrCannotOpenFile = 5004, // File opening error
|
||||
ErrFileCachebufferError = 5005, // Not enough memory for cache to read
|
||||
ErrCannotDeleteFile = 5006, // File deleting error
|
||||
ErrInvalidFilehandle = 5007, // A file with this handle was closed, or was not opening at all
|
||||
ErrWrongFilehandle = 5008, // Wrong file handle
|
||||
ErrFileNottowrite = 5009, // The file must be opened for writing
|
||||
ErrFileNottoread = 5010, // The file must be opened for reading
|
||||
ErrFileNotbin = 5011, // The file must be opened as a binary one
|
||||
ErrFileNottxt = 5012, // The file must be opened as a text
|
||||
ErrFileNottxtorcsv = 5013, // The file must be opened as a text or CSV
|
||||
ErrFileNotcsv = 5014, // The file must be opened as CSV
|
||||
ErrFileReaderror = 5015, // File reading error
|
||||
ErrFileBinstringsize = 5016, // String size must be specified, because the file is opened as binary
|
||||
ErrIncompatibleFile = 5017, // A text file must be for string arrays, for other arrays - binary
|
||||
ErrFileIsDirectory = 5018, // This is not a file, this is a directory
|
||||
ErrFileNotExist = 5019, // File does not exist
|
||||
ErrFileCannotRewrite = 5020, // File can not be rewritten
|
||||
ErrWrongDirectoryname = 5021, // Wrong directory name
|
||||
ErrDirectoryNotExist = 5022, // Directory does not exist
|
||||
ErrFileIsnotDirectory = 5023, // This is a file, not a directory
|
||||
ErrCannotDeleteDirectory = 5024, // The directory cannot be removed
|
||||
ErrCannotCleanDirectory = 5025, // Failed to clear the directory (probably one or more files are blocked and removal operation failed)
|
||||
ErrFileWriteerror = 5026, // Failed to write a resource to a file
|
||||
ErrFileEndoffile = 5027, // Unable to read the next piece of data from a CSV file (FileReadString, FileReadNumber, FileReadDatetime, FileReadBool), since the end of file is reached
|
||||
|
||||
ErrNoStringDate = 5030, // No date in the string
|
||||
ErrWrongStringDate = 5031, // Wrong date in the string
|
||||
ErrWrongStringTime = 5032, // Wrong time in the string
|
||||
ErrStringTimeError = 5033, // Error converting string to date
|
||||
ErrStringOutOfMemory = 5034, // Not enough memory for the string
|
||||
ErrStringSmallLen = 5035, // The string length is less than expected
|
||||
ErrStringTooBignumber = 5036, // Too large number, more than ULONG_MAX
|
||||
ErrWrongFormatstring = 5037, // Invalid format string
|
||||
ErrTooManyFormatters = 5038, // Amount of format specifiers more than the parameters
|
||||
ErrTooManyParameters = 5039, // Amount of parameters more than the format specifiers
|
||||
ErrWrongStringParameter = 5040, // Damaged parameter of string type
|
||||
ErrStringposOutofrange = 5041, // Position outside the string
|
||||
ErrStringZeroadded = 5042, // 0 added to the string end, a useless operation
|
||||
ErrStringUnknowntype = 5043, // Unknown data type when converting to a string
|
||||
ErrWrongStringObject = 5044, // Damaged string object
|
||||
|
||||
ErrIncompatibleArrays = 5050, // Copying incompatible arrays. String array can be copied only to a string array, and a numeric array - in numeric array only
|
||||
ErrSmallAsseriesArray = 5051, // The receiving array is declared as AS_SERIES, and it is of insufficient size
|
||||
ErrSmallArray = 5052, // Too small array, the starting position is outside the array
|
||||
ErrZerosizeArray = 5053, // An array of zero length
|
||||
ErrNumberArraysOnly = 5054, // Must be a numeric array
|
||||
ErrOnedimArraysOnly = 5055, // Must be a one-dimensional array
|
||||
ErrSeriesArray = 5056, // Timeseries cannot be used
|
||||
ErrDoubleArrayOnly = 5057, // Must be an array of type double
|
||||
ErrFloatArrayOnly = 5058, // Must be an array of type float
|
||||
ErrLongArrayOnly = 5059, // Must be an array of type long
|
||||
ErrIntArrayOnly = 5060, // Must be an array of type int
|
||||
ErrShortArrayOnly = 5061, // Must be an array of type short
|
||||
ErrCharArrayOnly = 5062, // Must be an array of type char
|
||||
|
||||
ErrOpenclNotSupported = 5100, // OpenCL functions are not supported on this computer
|
||||
ErrOpenclInternal = 5101, // Internal error occurred when running OpenCL
|
||||
ErrOpenclInvalidHandle = 5102, // Invalid OpenCL handle
|
||||
ErrOpenclContextCreate = 5103, // Error creating the OpenCL context
|
||||
ErrOpenclQueueCreate = 5104, // Failed to create a run queue in OpenCL
|
||||
ErrOpenclProgramCreate = 5105, // Error occurred when compiling an OpenCL program
|
||||
ErrOpenclTooLongKernelName = 5106, // Too long kernel name (OpenCL kernel)
|
||||
ErrOpenclKernelCreate = 5107, // Error creating an OpenCL kernel
|
||||
ErrOpenclSetKernelParameter = 5108, // Error occurred when setting parameters for the OpenCL kernel
|
||||
ErrOpenclExecute = 5109, // OpenCL program runtime error
|
||||
ErrOpenclWrongBufferSize = 5110, // Invalid size of the OpenCL buffer
|
||||
ErrOpenclWrongBufferOffset = 5111, // Invalid offset in the OpenCL buffer
|
||||
ErrOpenclBufferCreate = 5112, // Failed to create an OpenCL buffer
|
||||
|
||||
ErrWebrequestInvalidAddress = 5200, // Invalid URL
|
||||
ErrWebrequestConnectFailed = 5201, // Failed to connect to specified URL
|
||||
ErrWebrequestTimeout = 5202, // Timeout exceeded
|
||||
ErrWebrequestRequestFailed = 5203, // HTTP request failed
|
||||
|
||||
ErrUserErrorFirst = 65536 // User defined errors start with this code
|
||||
}
|
||||
}
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace MtApi5
|
||||
{
|
||||
public class ExecutionException: Exception
|
||||
{
|
||||
public ExecutionException(ErrorCode errorCode, string message)
|
||||
:base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
}
|
||||
|
||||
public ErrorCode ErrorCode { get; private set; }
|
||||
}
|
||||
}
|
||||
+13
-9
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MtApi5
|
||||
{
|
||||
@@ -9,17 +6,24 @@ namespace MtApi5
|
||||
{
|
||||
public MqlTick(DateTime time, double bid, double ask, double last, ulong volume)
|
||||
{
|
||||
this.time = time;
|
||||
MtTime = Mt5TimeConverter.ConvertToMtTime(time);
|
||||
this.bid = bid;
|
||||
this.ask = ask;
|
||||
this.last = last;
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public DateTime time { get; private set; } // Time of the last prices update
|
||||
public double bid { get; private set; } // Current Bid price
|
||||
public double ask { get; private set; } // Current Ask price
|
||||
public double last { get; private set; } // Price of the last deal (Last)
|
||||
public ulong volume { get; private set; } // Volume for the current Last price
|
||||
public MqlTick()
|
||||
{
|
||||
}
|
||||
|
||||
public long MtTime { get; set; } // Time of the last prices update
|
||||
|
||||
public double bid { get; set; } // Current Bid price
|
||||
public double ask { get; set; } // Current Ask price
|
||||
public double last { get; set; } // Price of the last deal (Last)
|
||||
public ulong volume { get; set; } // Volume for the current Last price
|
||||
|
||||
public DateTime time => Mt5TimeConverter.ConvertFromMtTime(MtTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,9 @@ namespace MtApi5
|
||||
BacktestingReady = 66,
|
||||
IsTesting = 67,
|
||||
|
||||
Print = 68
|
||||
Print = 68,
|
||||
|
||||
//Requests
|
||||
MtRequest = 155,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -43,6 +47,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CopyTicksFlag.cs" />
|
||||
<Compile Include="MqlBookInfo.cs" />
|
||||
<Compile Include="MqlRates.cs" />
|
||||
<Compile Include="MqlTick.cs" />
|
||||
@@ -58,8 +63,15 @@
|
||||
<Compile Include="MtApi5Client.cs" />
|
||||
<Compile Include="Mt5CommandType.cs" />
|
||||
<Compile Include="MtConverters.cs" />
|
||||
<Compile Include="ErrorCode.cs" />
|
||||
<Compile Include="ExecutionException.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Mt5Quote.cs" />
|
||||
<Compile Include="Requests\CopyTicksRequest.cs" />
|
||||
<Compile Include="Requests\RequestBase.cs" />
|
||||
<Compile Include="Requests\RequestType.cs" />
|
||||
<Compile Include="Responses\CopyTicksResponse.cs" />
|
||||
<Compile Include="Responses\ResponseBase.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MTApiService\MTApiService.csproj">
|
||||
@@ -67,6 +79,9 @@
|
||||
<Name>MTApiService</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
+62
-1
@@ -4,6 +4,9 @@ using System.Linq;
|
||||
using MTApiService;
|
||||
using System.Collections;
|
||||
using System.ServiceModel;
|
||||
using MtApi5.Requests;
|
||||
using MtApi5.Responses;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MtApi5
|
||||
{
|
||||
@@ -1200,6 +1203,24 @@ namespace MtApi5
|
||||
|
||||
return spreadArray?.Length ?? 0;
|
||||
}
|
||||
|
||||
|
||||
///<summary>
|
||||
///The function receives ticks in the MqlTick format into ticks_array. In this case, ticks are indexed from the past to the present, i.e. the 0 indexed tick is the oldest one in the array. For tick analysis, check the flags field, which shows what exactly has changed in the tick.
|
||||
///</summary>
|
||||
///<param name="symbolName">Symbol name.</param>
|
||||
///<param name="flags">The flag that determines the type of received ticks.</param>
|
||||
///<param name="from">The date from which you want to request ticks. In milliseconds since 1970.01.01. If from=0, the last count ticks will be returned.</param>
|
||||
///<param name="count">The number of ticks that you want to receive. If the 'from' and 'count' parameters are not specified, all available recent ticks (but not more than 2000) will be written to result.</param>
|
||||
///<see href="https://www.mql5.com/en/docs/series/copyticks"/>
|
||||
public List<MqlTick> CopyTicks(string symbolName, CopyTicksFlag flags = CopyTicksFlag.All, ulong from = 0, uint count = 0)
|
||||
{
|
||||
var response = SendRequest<CopyTicksResponse>(new CopyTicksRequest
|
||||
{
|
||||
SymbolName = symbolName, Flags = (int)flags, From = from, Count = count
|
||||
});
|
||||
return response.Ticks;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Market Info
|
||||
@@ -1302,7 +1323,7 @@ namespace MtApi5
|
||||
tick = null;
|
||||
if (retVal != null)
|
||||
{
|
||||
tick = new MqlTick(Mt5TimeConverter.ConvertFromMtTime(retVal.time), retVal.bid, retVal.ask, retVal.last, retVal.volume);
|
||||
tick = new MqlTick { MtTime = retVal.time, ask = retVal.ask, bid = retVal.bid, last = retVal.last, volume = retVal.volume };
|
||||
}
|
||||
|
||||
return tick != null;
|
||||
@@ -1512,6 +1533,46 @@ namespace MtApi5
|
||||
return responseValue != null ? (T) responseValue : default(T);
|
||||
}
|
||||
|
||||
private T SendRequest<T>(RequestBase request) where T : ResponseBase, new()
|
||||
{
|
||||
if (request == null)
|
||||
return default(T);
|
||||
|
||||
var serializer = JsonConvert.SerializeObject(request, Formatting.None,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
var commandParameters = new ArrayList { serializer };
|
||||
|
||||
MtResponseString res;
|
||||
try
|
||||
{
|
||||
lock (_client)
|
||||
{
|
||||
res = (MtResponseString)_client.SendCommand((int)Mt5CommandType.MtRequest, commandParameters);
|
||||
}
|
||||
}
|
||||
catch (CommunicationException ex)
|
||||
{
|
||||
throw new Exception(ex.Message, ex);
|
||||
}
|
||||
|
||||
if (res == null)
|
||||
{
|
||||
throw new ExecutionException(ErrorCode.ErrCustom, "Response from MetaTrader is null");
|
||||
}
|
||||
|
||||
var response = JsonConvert.DeserializeObject<T>(res.Value);
|
||||
if (response.ErrorCode != 0)
|
||||
{
|
||||
throw new ExecutionException((ErrorCode)response.ErrorCode, response.ErrorMessage);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
private void mClient_QuoteUpdated(MtQuote quote)
|
||||
{
|
||||
if (quote != null)
|
||||
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
namespace MtApi5.Requests
|
||||
{
|
||||
internal class CopyTicksRequest: RequestBase
|
||||
{
|
||||
public override RequestType RequestType => RequestType.CopyTicks;
|
||||
|
||||
public string SymbolName { get; set; }
|
||||
public int Flags { get; set; }
|
||||
public ulong From { get; set; }
|
||||
public uint Count { get; set; }
|
||||
}
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
namespace MtApi5.Requests
|
||||
{
|
||||
internal abstract class RequestBase
|
||||
{
|
||||
public abstract RequestType RequestType { get; }
|
||||
}
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
namespace MtApi5.Requests
|
||||
{
|
||||
internal enum RequestType
|
||||
{
|
||||
Unknown = 0,
|
||||
CopyTicks = 1
|
||||
}
|
||||
}
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MtApi5.Responses
|
||||
{
|
||||
internal class CopyTicksResponse: ResponseBase
|
||||
{
|
||||
public List<MqlTick> Ticks { get; set; }
|
||||
}
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
namespace MtApi5.Responses
|
||||
{
|
||||
internal class ResponseBase
|
||||
{
|
||||
public int ErrorCode { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?define ProductName="MtApi5" ?>
|
||||
|
||||
<?if $(var.Platform) = x64 ?>
|
||||
<?define ProductName="MtApi5_x64" ?>
|
||||
<?define ProductPathNative="..\build\products\$(var.Configuration)\x64\"?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?define PlatformSystemFolder = "System64Folder" ?>
|
||||
<?else ?>
|
||||
<?define ProductName="MtApi5" ?>
|
||||
<?define ProductPathNative="..\build\products\$(var.Configuration)\"?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?define PlatformSystemFolder = "SystemFolder" ?>
|
||||
@@ -78,6 +78,11 @@
|
||||
</RegistryKey>
|
||||
|
||||
</Component>
|
||||
|
||||
<Component Id="NewtonsoftJsondll" Directory="INSTALLFOLDER">
|
||||
<File Id="Newtonsoft.Json.dll" Name="Newtonsoft.Json.dll" KeyPath="yes"
|
||||
src="$(var.ProductPath)Newtonsoft.Json.dll"/>
|
||||
</Component>
|
||||
|
||||
<Component Id="MTApiServiceDll" Directory="INSTALLFOLDER">
|
||||
<File Id="GAC_MTApiService.dll" Name="MTApiService.dll" KeyPath="yes"
|
||||
|
||||
@@ -334,9 +334,9 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox Grid.Row="0" Text="symbolName"/>
|
||||
<TextBox Grid.Row="1" Text="timeframe"/>
|
||||
<TextBox Grid.Row="2" Text="startPos"/>
|
||||
<TextBox Grid.Row="3" Text="count"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="0" Text="timeframe"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="0" Text="startPos"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="0" Text="count"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TimeSeriesValues.SymbolValue}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1"
|
||||
ItemsSource="{Binding Source={StaticResource ENUM_TIMEFRAMES_Key}}"
|
||||
@@ -344,25 +344,27 @@
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TimeSeriesValues.StartPos}"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding TimeSeriesValues.Count}"/>
|
||||
|
||||
<WrapPanel Grid.Row="4" Grid.ColumnSpan="2" Margin="10">
|
||||
<Button Command="{Binding CopyRatesCommand}"
|
||||
<WrapPanel Grid.Row="4" Grid.ColumnSpan="2" Grid.Column="0" Margin="10">
|
||||
<Button Command="{Binding CopyRatesCommand}" Margin="1"
|
||||
Content="CopyRates" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyTimesCommand}"
|
||||
<Button Command="{Binding CopyTimesCommand}" Margin="1"
|
||||
Content="CopyTimes" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyOpenCommand}"
|
||||
<Button Command="{Binding CopyOpenCommand}" Margin="1"
|
||||
Content="CopyOpen" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyHighCommand}"
|
||||
<Button Command="{Binding CopyHighCommand}" Margin="1"
|
||||
Content="CopyHigh" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyLowCommand}"
|
||||
<Button Command="{Binding CopyLowCommand}" Margin="1"
|
||||
Content="CopyLow" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyCloseCommand}"
|
||||
<Button Command="{Binding CopyCloseCommand}" Margin="1"
|
||||
Content="CopyClose" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyTickVolumeCommand}"
|
||||
<Button Command="{Binding CopyTickVolumeCommand}" Margin="1"
|
||||
Content="CopyTickVolume" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyRealVolumeCommand}"
|
||||
<Button Command="{Binding CopyRealVolumeCommand}" Margin="1"
|
||||
Content="CopyRealVolume" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopySpreadCommand}"
|
||||
<Button Command="{Binding CopySpreadCommand}" Margin="1"
|
||||
Content="CopySpread" HorizontalAlignment="Left" />
|
||||
<Button Command="{Binding CopyTicksCommand}" Margin="1"
|
||||
Content="CopyTicks" HorizontalAlignment="Left" />
|
||||
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace MtApi5TestClient
|
||||
public DelegateCommand CopyTickVolumeCommand { get; private set; }
|
||||
public DelegateCommand CopyRealVolumeCommand { get; private set; }
|
||||
public DelegateCommand CopySpreadCommand { get; private set; }
|
||||
public DelegateCommand CopyTicksCommand { get; private set; }
|
||||
|
||||
public DelegateCommand SymbolsTotalCommand { get; private set; }
|
||||
public DelegateCommand SymbolNameCommand { get; private set; }
|
||||
@@ -207,7 +208,8 @@ namespace MtApi5TestClient
|
||||
CopyTickVolumeCommand = new DelegateCommand(ExecuteCopyTickVolume);
|
||||
CopyRealVolumeCommand = new DelegateCommand(ExecuteCopyRealVolume);
|
||||
CopySpreadCommand = new DelegateCommand(ExecuteCopySpread);
|
||||
|
||||
CopyTicksCommand = new DelegateCommand(ExecuteCopyTicks);
|
||||
|
||||
SymbolsTotalCommand = new DelegateCommand(ExecuteSymbolsTotal);
|
||||
SymbolNameCommand = new DelegateCommand(ExecuteSymbolName);
|
||||
SymbolSelectCommand = new DelegateCommand(ExecuteSymbolSelect);
|
||||
@@ -569,6 +571,32 @@ namespace MtApi5TestClient
|
||||
AddLog("CopySpread: success");
|
||||
}
|
||||
|
||||
private async void ExecuteCopyTicks(object o)
|
||||
{
|
||||
if (string.IsNullOrEmpty(TimeSeriesValues?.SymbolValue)) return;
|
||||
|
||||
TimeSeriesResults.Clear();
|
||||
|
||||
var result = await Execute(() => _mtApiClient.CopyTicks(TimeSeriesValues.SymbolValue));
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
AddLog("CopyTicks: result is null");
|
||||
return;
|
||||
}
|
||||
|
||||
AddLog("CopyTicks: success.");
|
||||
|
||||
RunOnUiThread(() =>
|
||||
{
|
||||
foreach (var v in result)
|
||||
{
|
||||
var tickStr = $"time = {v.time}, bid = {v.bid}, ask = {v.ask}, last = {v.last}, volume = {v.volume}";
|
||||
TimeSeriesResults.Add(tickStr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void ExecuteSymbolsTotal(object o)
|
||||
{
|
||||
var selectedCount = await Execute(() => _mtApiClient.SymbolsTotal(true));
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+133
@@ -1,6 +1,7 @@
|
||||
#property copyright "Vyacheslav Demidyuk"
|
||||
#property link ""
|
||||
|
||||
#include <json.mqh>
|
||||
#include <Trade\SymbolInfo.mqh>
|
||||
#include <trade/trade.mqh>
|
||||
#property version "1.1"
|
||||
@@ -47,6 +48,7 @@ bool IsRemoteReadyForTesting = false;
|
||||
|
||||
string symbolValue;
|
||||
string commentValue;
|
||||
string requestValue;
|
||||
|
||||
string PARAM_SEPARATOR = ";";
|
||||
|
||||
@@ -77,6 +79,7 @@ int preinit()
|
||||
StringInit(message, 1000, 0);
|
||||
StringInit(symbolValue, 1000, 0);
|
||||
StringInit(commentValue, 1000, 0);
|
||||
StringInit(requestValue, 1000, 0);
|
||||
|
||||
return (0);
|
||||
}
|
||||
@@ -231,6 +234,25 @@ int executeCommand()
|
||||
case 0:
|
||||
//NoCommand
|
||||
break;
|
||||
|
||||
case 155: //Request
|
||||
{
|
||||
if (!getStringValue(ExpertHandle, 0, requestValue))
|
||||
{
|
||||
PrintParamError("Request");
|
||||
}
|
||||
|
||||
string response = "";
|
||||
|
||||
if (requestValue != "")
|
||||
{
|
||||
Print("executeCommand: incoming request = ", requestValue);
|
||||
response = OnRequest(requestValue);
|
||||
}
|
||||
|
||||
sendStringResponse(ExpertHandle, response);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // OrderSend
|
||||
{
|
||||
@@ -1830,3 +1852,114 @@ bool OrderCloseAll()
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
string OnRequest(string json)
|
||||
{
|
||||
string response = "";
|
||||
|
||||
JSONParser *parser = new JSONParser();
|
||||
JSONValue *jv = parser.parse(json);
|
||||
|
||||
if(jv == NULL)
|
||||
{
|
||||
PrintFormat("OnRequest [ERROR]: %d - %s", (string)parser.getErrorCode(), parser.getErrorMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
if(jv.isObject())
|
||||
{
|
||||
JSONObject *jo = jv;
|
||||
int requestType = jo.getInt("RequestType");
|
||||
|
||||
switch(requestType)
|
||||
{
|
||||
case 1: //CopyTicks
|
||||
response = ExecuteRequest_CopyTicks(jo);
|
||||
break;
|
||||
default:
|
||||
PrintFormat("OnRequest [WARNING]: Unknown request type %d", requestType);
|
||||
response = CreateErrorResponse(-1, "Unknown request type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete jv;
|
||||
}
|
||||
|
||||
delete parser;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
string CreateErrorResponse(int code, string message)
|
||||
{
|
||||
JSONValue* jsonError;
|
||||
if (code == 0)
|
||||
jsonError = new JSONString("0");
|
||||
else
|
||||
jsonError = new JSONNumber((long)code);
|
||||
|
||||
JSONObject *joResponse = new JSONObject();
|
||||
joResponse.put("ErrorCode", jsonError);
|
||||
joResponse.put("ErrorMessage", new JSONString(message));
|
||||
|
||||
string result = joResponse.toString();
|
||||
delete joResponse;
|
||||
return result;
|
||||
}
|
||||
|
||||
string CreateSuccessResponse(string responseName, JSONValue* responseBody)
|
||||
{
|
||||
JSONObject *joResponse = new JSONObject();
|
||||
joResponse.put("ErrorCode", new JSONString("0"));
|
||||
|
||||
if (responseBody != NULL)
|
||||
{
|
||||
joResponse.put(responseName, responseBody);
|
||||
}
|
||||
|
||||
string result = joResponse.toString();
|
||||
delete joResponse;
|
||||
return result;
|
||||
}
|
||||
|
||||
JSONObject* Serialize(MqlTick& tick)
|
||||
{
|
||||
JSONObject *jo = new JSONObject();
|
||||
jo.put("MtTime", new JSONNumber(tick.time));
|
||||
jo.put("bid", new JSONNumber(tick.bid));
|
||||
jo.put("ask", new JSONNumber(tick.ask));
|
||||
jo.put("last", new JSONNumber(tick.last));
|
||||
jo.put("volume", new JSONNumber(tick.volume));
|
||||
return jo;
|
||||
}
|
||||
|
||||
string ExecuteRequest_CopyTicks(JSONObject *jo)
|
||||
{
|
||||
if (jo.getValue("SymbolName") == NULL)
|
||||
return CreateErrorResponse(-1, "Undefinded mandatory parameter SymbolName");
|
||||
if (jo.getValue("Flags") == NULL)
|
||||
return CreateErrorResponse(-1, "Undefinded mandatory parameter Flags");
|
||||
if (jo.getValue("From") == NULL)
|
||||
return CreateErrorResponse(-1, "Undefinded mandatory parameter From");
|
||||
if (jo.getValue("Count") == NULL)
|
||||
return CreateErrorResponse(-1, "Undefinded mandatory parameter Count");
|
||||
|
||||
string symbol = jo.getString("SymbolName");
|
||||
uint flags = jo.getInt("Flags");
|
||||
int from = jo.getInt("From");
|
||||
int count = jo.getInt("Count");
|
||||
|
||||
MqlTick ticks[];
|
||||
int received = CopyTicks(symbol, ticks, flags, from, count);
|
||||
if(received == -1)
|
||||
return CreateErrorResponse(GetLastError(), "CopyTicks failed");
|
||||
|
||||
JSONArray* jaTicks = new JSONArray();
|
||||
for(int i = 0; i < received; i++)
|
||||
{
|
||||
jaTicks.put(i, Serialize(ticks[i]));
|
||||
}
|
||||
|
||||
return CreateSuccessResponse("Ticks", jaTicks);;
|
||||
}
|
||||
Executable
+672
@@ -0,0 +1,672 @@
|
||||
// $Id: hash.mqh 125 2014-03-03 08:38:32Z ydrol $
|
||||
#ifndef YDROL_HASH_MQH
|
||||
#define YDROL_HASH_MQH
|
||||
|
||||
//#property strict
|
||||
|
||||
/*
|
||||
This is losely ported from a C version I have which was in turn modified from hashtable.c by Christopher Clark.
|
||||
Copyright (C) 2014, Andrew Lord (NICKNAME=lordy) <forex@NICKNAME.org.uk>
|
||||
Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
|
||||
|
||||
2014/02/21 - Readded PrimeNumber sizes and auto rehashing when load factor hit.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/// Any value stored in a Hash must be a subclass of HashValue
|
||||
class HashValue {
|
||||
};
|
||||
|
||||
/// Linked list of values - there will be one list for each hash value
|
||||
class HashEntry {
|
||||
public:
|
||||
string _key;
|
||||
HashValue * _val;
|
||||
HashEntry *_next;
|
||||
|
||||
HashEntry() {
|
||||
_key=NULL;
|
||||
_val=NULL;
|
||||
_next=NULL;
|
||||
}
|
||||
|
||||
HashEntry(string key,HashValue* val) {
|
||||
_key=key;
|
||||
_val=val;
|
||||
_next=NULL;
|
||||
}
|
||||
|
||||
~HashEntry() {
|
||||
}
|
||||
};
|
||||
|
||||
/// Convenience class for storing strings as hash values.
|
||||
class HashString : public HashValue {
|
||||
private:
|
||||
string val;
|
||||
public:
|
||||
HashString(string v) { val=v;}
|
||||
string getVal() { return val; }
|
||||
};
|
||||
|
||||
/// Convenience class for storing doubles as hash values.
|
||||
class HashDouble : public HashValue {
|
||||
private:
|
||||
double val;
|
||||
public:
|
||||
HashDouble(double v) { val=v;}
|
||||
double getVal() { return val; }
|
||||
};
|
||||
|
||||
/// Convenience class for storing ints as hash values.
|
||||
class HashInt : public HashValue {
|
||||
private:
|
||||
int val;
|
||||
public:
|
||||
HashInt(int v) { val=v;}
|
||||
int getVal() { return val; }
|
||||
};
|
||||
|
||||
/// Convenience class for storing longs as hash values.
|
||||
class HashLong : public HashValue {
|
||||
private:
|
||||
long val;
|
||||
public:
|
||||
HashLong(datetime v) { val=v;}
|
||||
long getVal() { return val; }
|
||||
|
||||
};
|
||||
|
||||
/// Convenience class for storing datetimes as hash values.
|
||||
class HashDatetime : public HashValue {
|
||||
private:
|
||||
datetime val;
|
||||
public:
|
||||
HashDatetime(datetime v) { val=v;}
|
||||
datetime getVal() { return val; }
|
||||
};
|
||||
|
||||
///
|
||||
/// Hash class allows objects to be stored in a table index by strings.
|
||||
/// the stored Objects must be a sub class of the HashValue class.
|
||||
///
|
||||
/// There are some convenience classes to hold atomic types as values HashString,HashDouble,HashInt
|
||||
///
|
||||
///EXAMPLE:
|
||||
///
|
||||
/// <pre>
|
||||
/// class myClass: public HashValue {
|
||||
/// public: int v;
|
||||
/// myClass(int a) { v = a;}
|
||||
/// };
|
||||
///
|
||||
/// // Create the objects as needed
|
||||
///
|
||||
/// myClass *a = new myClass(1);
|
||||
/// myClass *b = new myClass(2);
|
||||
/// myClass *c = new myClass(3);
|
||||
///
|
||||
/// // Then to insert into hash etc.
|
||||
///
|
||||
/// Hash* h = new Hash(193,true);
|
||||
/// // 'true' means when the hash will adopt the values and delete them when they are removed from the hash or when the hash is deleted.
|
||||
///
|
||||
/// h.hPut("a",a);
|
||||
/// h.hPut("b",b);
|
||||
/// h.hPut("c",c);
|
||||
///
|
||||
/// myClass *d = h.hGet("b");
|
||||
///
|
||||
/// etc.
|
||||
///
|
||||
/// // Iterate over hash
|
||||
/// HashLoop *l
|
||||
/// for (l = new HashLoop(h) ; l.hasNext() ; l.next() ) {
|
||||
/// string key = l.key();
|
||||
/// MyClass *c = l.val();
|
||||
/// }
|
||||
/// delete l;
|
||||
///
|
||||
/// // Delete from hash - This will also delete 'a' because we set the 'adopt' flag on the hash.
|
||||
/// h.hDel("a");
|
||||
///
|
||||
/// //Delete the hash - this will also delete 'b' and 'c' because of the adopt flag.
|
||||
/// delete h;
|
||||
/// </pre>
|
||||
class Hash : public HashValue {
|
||||
|
||||
private:
|
||||
/// Number of slots in the hashtable.
|
||||
/// this should be approx number of elements to store. Depending on hash algorithm
|
||||
/// it may optimally be a prime or a power of two etc. but probably not important
|
||||
/// for MQL4 performance. A future optimisation might be to move the hashcode function to a DLL??
|
||||
uint _hashSlots;
|
||||
|
||||
/// Number of elements at which hash will get resized.
|
||||
int _resizeThreshold;
|
||||
|
||||
/// number of things in the hash
|
||||
int _hashEntryCount;
|
||||
|
||||
/// an array of linked lists (HashEntry). one for each hash value.
|
||||
/// To store an object against a string(key) - get the string hashcode, then insert pair (key,val) into the linked list for that hashcode.
|
||||
/// To fetch an object against a string(key) - get the string hashcode, get linked-list at that hashcode index, then search for the key and return the val.
|
||||
HashEntry* _buckets[];
|
||||
|
||||
/// If true the hash will free(delete) values as they are removed, or at cleanup.
|
||||
bool _adoptValues;
|
||||
|
||||
int _errCode;
|
||||
string _errText;
|
||||
|
||||
void init(uint size,bool adoptValues)
|
||||
{
|
||||
_hashSlots = 0;
|
||||
_hashEntryCount = 0;
|
||||
clearError();
|
||||
setAdoptValues(adoptValues);
|
||||
|
||||
rehash(size);
|
||||
}
|
||||
|
||||
// Hash table distribution is better when size is prime, eg if hash function procduces numbers
|
||||
// that are multiples of x, then there may be grouping occuring around gcd(x,slots) gcd(2x,slots) etc
|
||||
// using a prime size helps spread the distribution.
|
||||
uint size2prime(uint size) {
|
||||
int pmax=ArraySize(_primes);
|
||||
for(int p=0 ; p<pmax; p++ ) {
|
||||
if (_primes[p] >= size) {
|
||||
return _primes[p];
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/// Primes that approx double in size, used for hash table sizes to avoid gcd causing bunching
|
||||
static uint _primes[];
|
||||
|
||||
/// After reviewing quite a few hash functions I settled on the one below.
|
||||
/// http://www.cse.yorku.ca/~oz/hash.html
|
||||
/// this is the bottleneck function. Shame mql hash no default hash method for objects.
|
||||
uint hash(string s)
|
||||
{
|
||||
|
||||
uchar c[];
|
||||
uint h = 0;
|
||||
|
||||
if (s != NULL) {
|
||||
h = 5381;
|
||||
int n = StringToCharArray(s,c);
|
||||
for(int i = 0 ; i < n ; i++ ) {
|
||||
h = ((h << 5 ) + h ) + c[i];
|
||||
}
|
||||
}
|
||||
return h % _hashSlots;
|
||||
}
|
||||
void clearError() {
|
||||
setError(0,"");
|
||||
}
|
||||
void setError(int e,string m) {
|
||||
_errCode = e;
|
||||
_errText = m;
|
||||
//error((string)e,m);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Constructor: Create a Hash Object
|
||||
Hash() {
|
||||
init(17,true);
|
||||
}
|
||||
|
||||
|
||||
/// Constructor: Create a Hash Object
|
||||
/// @param adoptValues : If true the hash destructor will <b>delete</b> all dynamically allocated hash values.
|
||||
Hash(bool adoptValues) {
|
||||
init(17,adoptValues);
|
||||
}
|
||||
|
||||
/// Constructor: Create a Hash Object
|
||||
/// @param size : Approximate size (actual size will be a larger prime number close to a power of 2)
|
||||
/// @param adoptValues : If true the hash destructor will <b>delete</b> all dynamically allocated hash values.
|
||||
Hash(int size,bool adoptValues) {
|
||||
init(size,adoptValues);
|
||||
}
|
||||
|
||||
~Hash() {
|
||||
|
||||
// Free entries.
|
||||
for(uint i = 0 ; i< _hashSlots ; i++) {
|
||||
HashEntry *nextEntry = NULL;
|
||||
for(HashEntry *entry = _buckets[i] ; entry!= NULL ; entry = nextEntry )
|
||||
{
|
||||
nextEntry = entry._next;
|
||||
|
||||
if (_adoptValues && entry._val != NULL && CheckPointer(entry._val) == POINTER_DYNAMIC ) {
|
||||
delete entry._val;
|
||||
}
|
||||
delete entry;
|
||||
}
|
||||
_buckets[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return any error that has occured. This should be used when
|
||||
/// retriving values in a Hash that may contain NULLs. hGet()
|
||||
/// methods can return NULL if not found, in which case getErrorCode
|
||||
/// will be set.
|
||||
int getErrCode() {
|
||||
return _errCode;
|
||||
}
|
||||
/// Return text of the error message.
|
||||
string getErrText() {
|
||||
return _errText;
|
||||
}
|
||||
|
||||
/// If true the hash destructor will <b>delete</b> all dynamically allocated hash values.
|
||||
void setAdoptValues(bool v) {
|
||||
_adoptValues = v;
|
||||
}
|
||||
|
||||
/// True if the hash destructor will <b>delete</b> all dynamically allocated hash values.
|
||||
bool getAdoptValues() {
|
||||
return _adoptValues;
|
||||
}
|
||||
|
||||
private:
|
||||
uint _foundIndex; // After find() is called is set to hashindex for name whether found or not.
|
||||
HashEntry* _foundEntry; // After find() is called is set to the HashEntry that contains the key.
|
||||
HashEntry* _foundPrev; // After find() is called is set to the HashEntry before the entry
|
||||
// (could use double linked list but requires more memory).
|
||||
|
||||
/// Look for the required entry for key 'name' true if found.
|
||||
bool find(string keyName) {
|
||||
|
||||
//Alert("finding");
|
||||
bool found = false;
|
||||
|
||||
// Get the index using the hashcode of the string
|
||||
_foundIndex = hash(keyName);
|
||||
|
||||
|
||||
if (_foundIndex>_hashSlots ) {
|
||||
|
||||
setError(1,"hGet: bad hashIndex="+(string)_foundIndex+" size "+(string)_hashSlots);
|
||||
|
||||
} else {
|
||||
|
||||
// Search the linked list determined by the index.
|
||||
|
||||
for(HashEntry *e = _buckets[_foundIndex] ; e != NULL ; e = e._next ) {
|
||||
if (e._key == keyName) {
|
||||
_foundEntry = e;
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
// Track the item before the target item in case deleting from single linked list.
|
||||
_foundPrev = e;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// This is used by the HashLoop class to get start of LinkedList at bucket[i]
|
||||
HashEntry*getEntry(int i) {
|
||||
return _buckets[i];
|
||||
}
|
||||
|
||||
/// Return the number of slots/buckets (not number of elements)
|
||||
uint getSlots() {
|
||||
return _hashSlots;
|
||||
}
|
||||
/// Return the number of elements in the Hash
|
||||
int getCount() {
|
||||
return _hashEntryCount;
|
||||
}
|
||||
|
||||
/// Change the hash size and re-allocate values to new buckets.
|
||||
bool rehash(uint newSize) {
|
||||
bool ret = false;
|
||||
HashEntry* oldTable[];
|
||||
|
||||
uint oldSize = _hashSlots;
|
||||
newSize = size2prime(newSize);
|
||||
//info("rehashing from "+(string)_hashSlots+" to "+(string)newSize+" "+(string)GetTickCount());
|
||||
|
||||
if (newSize <= getSlots()) {
|
||||
setError(2,"rehash "+(string)newSize+" <= "+(string)_hashSlots);
|
||||
} else if (ArrayResize(_buckets,newSize) != newSize) {
|
||||
setError(3,"unable to resize ");
|
||||
} else if (ArrayResize(oldTable,oldSize) != oldSize) {
|
||||
setError(4,"unable to resize old copy ");
|
||||
} else {
|
||||
//Copy old table.
|
||||
uint i;
|
||||
for(i = 0 ; i < oldSize ; i++ ) oldTable[i] = _buckets[i];
|
||||
// Init new entries - not sure if MQL does this anyway
|
||||
for(i = 0 ; i<newSize ; i++ ) _buckets[i] = NULL;
|
||||
|
||||
// Move entries to new slots
|
||||
_hashSlots = newSize;
|
||||
_resizeThreshold = (int)_hashSlots / 4 * 3; // Just use the default load factor value of Javas HashTable
|
||||
|
||||
// Look through all slots
|
||||
for(uint oldHashCode = 0 ; oldHashCode<oldSize ; oldHashCode++ ) {
|
||||
HashEntry *next = NULL;
|
||||
|
||||
// Walk linked list
|
||||
for(HashEntry *e = oldTable[oldHashCode] ; e != NULL ; e = next ) {
|
||||
|
||||
next = e._next;
|
||||
|
||||
uint newHashCode = hash(e._key);
|
||||
// Insert at head of new list.
|
||||
e._next = _buckets[newHashCode];
|
||||
_buckets[newHashCode] = e;
|
||||
}
|
||||
|
||||
oldTable[oldHashCode] = NULL;
|
||||
}
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Check if the hash contains the given key
|
||||
/// @param keyName : The key
|
||||
/// @return: true if found otherwise false
|
||||
bool hContainsKey(string keyName) {
|
||||
return find(keyName);
|
||||
}
|
||||
|
||||
/// Fetch a value using string key
|
||||
/// @return :HashValue associated with the key (or NULL if none found)
|
||||
/// If the Hashtable contains legitimate NULL values then also check errCode()
|
||||
/// Examples:
|
||||
/// If not storing nulls use
|
||||
/// obj = hash.hGet(x); if (obj != NULL) OK
|
||||
///
|
||||
/// If storing nulls use
|
||||
/// obj = hash.hGet(x); if (obj != NULL || hash.errCode() == 0 ) OK
|
||||
HashValue* hGet(string keyName) {
|
||||
|
||||
HashValue *obj = NULL;
|
||||
clearError();
|
||||
bool found=false;
|
||||
|
||||
if (find(keyName)) {
|
||||
obj = _foundEntry._val;
|
||||
} else {
|
||||
//If Hash contains nulls then also check the errorCode=0 when retrieving
|
||||
if (!found) {
|
||||
setError(1,"not found");
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// Convenience method for getting values from a HashString value (see hPutString())
|
||||
string hGetString(string keyName) {
|
||||
string ret = NULL;
|
||||
HashString *v = hGet(keyName);
|
||||
if (v != NULL) {
|
||||
ret = v.getVal();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/// Convenience method for getting values from a HashDouble value (see hPutDouble())
|
||||
double hGetDouble(string keyName) {
|
||||
double ret = NULL;
|
||||
HashDouble *v = hGet(keyName);
|
||||
if (v != NULL) {
|
||||
ret = v.getVal();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/// Convenience method for getting values from a HashInt value (see hPutInt())
|
||||
int hGetInt(string keyName) {
|
||||
int ret = NULL;
|
||||
HashInt *v = hGet(keyName);
|
||||
if (v != NULL) {
|
||||
ret = v.getVal();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/// Convenience method for getting values from a HashLong ( see hPutLong())
|
||||
long hGetLong(string keyName) {
|
||||
long ret = NULL;
|
||||
HashLong *v = hGet(keyName);
|
||||
if (v != NULL) {
|
||||
ret = v.getVal();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/// Convenience method for getting values from a HashDatetime ( see hPutDatetime())
|
||||
datetime hGetDatetime(string keyName) {
|
||||
datetime ret = NULL;
|
||||
HashDatetime *v = hGet(keyName);
|
||||
if (v != NULL) {
|
||||
ret = v.getVal();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Store a hash value against the <b>keyName</b> key. This will overwrite any existing
|
||||
/// value. It adoptValues is set, it will also free the value if applicable.
|
||||
/// @param keyName : key name
|
||||
/// @param obj : Value to store
|
||||
/// @return the previous value of the key or NULL if there wasnt one
|
||||
HashValue *hPut(string keyName,HashValue *obj) {
|
||||
|
||||
HashValue *ret = NULL;
|
||||
clearError();
|
||||
|
||||
if (find(keyName)) {
|
||||
// Return revious value
|
||||
ret = _foundEntry._val;
|
||||
/*
|
||||
// Replace entry contents
|
||||
if (_adoptValues && _foundEntry._val != NULL && CheckPointer(_foundEntry._val) == POINTER_DYNAMIC ) {
|
||||
delete _foundEntry._val;
|
||||
}
|
||||
*/
|
||||
_foundEntry._val = obj;
|
||||
|
||||
} else {
|
||||
// Insert new entry at head of list
|
||||
HashEntry* e = new HashEntry(keyName,obj);
|
||||
HashEntry* first = _buckets[_foundIndex];
|
||||
e._next = first;
|
||||
_buckets[_foundIndex] = e;
|
||||
_hashEntryCount++;
|
||||
|
||||
//info((string)_hashEntryCount+" vs. "+(string)_resizeThreshold);
|
||||
// Auto Resize if number of entries hits _resizeThreshold
|
||||
if (_hashEntryCount > _resizeThreshold ) {
|
||||
rehash(_hashSlots/2*3); // this will snap to the next prime
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/// Store a string as hash value (HashString)
|
||||
/// @return the previous value of the key or NULL if there wasnt one
|
||||
HashValue* hPutString(string keyName,string s) {
|
||||
HashString *v = new HashString(s);
|
||||
return hPut(keyName,v);
|
||||
}
|
||||
/// Store a double as hash value (HashDouble)
|
||||
/// @return the previous value of the key or NULL if there wasnt one
|
||||
HashValue* hPutDouble(string keyName,double d) {
|
||||
HashDouble *v = new HashDouble(d);
|
||||
return hPut(keyName,v);
|
||||
}
|
||||
/// Store an int as hash value (HashInt)
|
||||
/// @return the previous value of the key or NULL if there wasnt one
|
||||
HashValue* hPutInt(string keyName,int i) {
|
||||
HashInt *v = new HashInt(i);
|
||||
return hPut(keyName,v);
|
||||
}
|
||||
|
||||
/// Store a datetime as hash value (HashLong)
|
||||
/// @return the previous value of the key or NULL if there wasnt one
|
||||
HashValue* hPutLong(string keyName,long i) {
|
||||
HashLong *v = new HashLong(i);
|
||||
return hPut(keyName,v);
|
||||
}
|
||||
|
||||
/// Store a datetime as hash value (HashDatetime)
|
||||
/// @return the previous value of the key or NULL if there wasnt one
|
||||
HashValue* hPutDatetime(string keyName,datetime i) {
|
||||
HashDatetime *v = new HashDatetime(i);
|
||||
return hPut(keyName,v);
|
||||
}
|
||||
|
||||
/// Delete an entry from the hash.
|
||||
bool hDel(string keyName) {
|
||||
|
||||
bool found = false;
|
||||
clearError();
|
||||
|
||||
if (find(keyName)) {
|
||||
HashEntry *next = _foundEntry._next;
|
||||
if (_foundPrev != NULL) {
|
||||
//Remove entry from the middle of the list.
|
||||
_foundPrev._next = next;
|
||||
} else {
|
||||
// remove from head of list
|
||||
_buckets[_foundIndex] = next;
|
||||
}
|
||||
|
||||
if (_adoptValues && _foundEntry._val != NULL&& CheckPointer(_foundEntry._val) == POINTER_DYNAMIC) {
|
||||
delete _foundEntry._val;
|
||||
}
|
||||
delete _foundEntry;
|
||||
_hashEntryCount--;
|
||||
found=true;
|
||||
|
||||
}
|
||||
return found;
|
||||
}
|
||||
};
|
||||
uint Hash::_primes[] = {
|
||||
17, 53, 97, 193, 389,
|
||||
769, 1543, 3079, 6151,
|
||||
12289, 24593, 49157, 98317,
|
||||
196613, 393241, 786433, 1572869,
|
||||
3145739, 6291469, 12582917, 25165843,
|
||||
50331653, 100663319, 201326611, 402653189,
|
||||
805306457, 1610612741};
|
||||
|
||||
/// Class to iterate over a Hash using ...
|
||||
/// <pre>
|
||||
/// HashLoop *l
|
||||
/// for (l = new HashLoop(h) ; l.hasNext() ; l.next() ) {
|
||||
/// string key = l.key();
|
||||
/// MyClass *c = l.val();
|
||||
/// }
|
||||
/// delete l;
|
||||
/// </pre>
|
||||
class HashLoop {
|
||||
private:
|
||||
uint _index;
|
||||
HashEntry *_currentEntry;
|
||||
Hash *_hash;
|
||||
|
||||
public:
|
||||
/// Create iterator for a hash - move to first item
|
||||
HashLoop(Hash *h) {
|
||||
setHash(h);
|
||||
}
|
||||
~HashLoop() {};
|
||||
|
||||
/// Clear current state and move to first item (if any).
|
||||
void reset() {
|
||||
_index=0;
|
||||
_currentEntry = _hash.getEntry(_index);
|
||||
|
||||
// Move to first item
|
||||
if (_currentEntry == NULL) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the hash over which to iterate.
|
||||
void setHash(Hash *h) {
|
||||
_hash = h;
|
||||
reset();
|
||||
}
|
||||
|
||||
/// Check if more items.
|
||||
bool hasNext() {
|
||||
bool ret = ( _currentEntry != NULL);
|
||||
//config("hasNext=",ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Move to next item.
|
||||
void next() {
|
||||
|
||||
//config("next : index = ",_index);
|
||||
|
||||
// Advance
|
||||
if (_currentEntry != NULL) {
|
||||
_currentEntry = _currentEntry._next;
|
||||
}
|
||||
|
||||
// Keep advancing if _currentEntry is null
|
||||
while (_currentEntry==NULL) {
|
||||
_index++;
|
||||
if (_index >= _hash.getSlots() ) return ;
|
||||
_currentEntry = _hash.getEntry(_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the key name of the current item.
|
||||
string key() {
|
||||
if (_currentEntry != NULL) {
|
||||
return _currentEntry._key;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the value.
|
||||
HashValue *val() {
|
||||
if (_currentEntry != NULL) {
|
||||
return _currentEntry._val;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience functions for retriving int from a current HashInt entry
|
||||
int valInt() {
|
||||
return ((HashInt *)val()).getVal();
|
||||
}
|
||||
|
||||
/// Convenience functions for retriving int from a current HashString entry
|
||||
string valString() {
|
||||
return ((HashString *)val()).getVal();
|
||||
}
|
||||
|
||||
/// Convenience functions for retriving int from a current HashDouble entry
|
||||
double valDouble() {
|
||||
return ((HashDouble *)val()).getVal();
|
||||
}
|
||||
|
||||
/// Convenience functions for retriving int from a current HashLong entry
|
||||
long valLong() {
|
||||
return ((HashLong *)val()).getVal();
|
||||
}
|
||||
/// Convenience functions for retriving int from a current HashDatetime entry
|
||||
datetime valDatetime() {
|
||||
return ((HashDatetime *)val()).getVal();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
Executable
+1033
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user