Skip to content

Commit

Permalink
upgrade trade server
Browse files Browse the repository at this point in the history
  • Loading branch information
chenli committed Aug 16, 2018
1 parent e83385c commit e6599e3
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 81 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# shinny-futures-android
一个开源的 android 平台期货行情交易终端</br>
## 预览
### Preview
<img src="screenshot/主力合约.jpg" width="280"/> <img src="screenshot/信息.jpg" width="280"/> <img src="screenshot/交易.jpg" width="280"/><br>
## 功能架构
### Install
*快速点亮:* 您可以点击代码仓库的`Release`标签,安装相应版本的apk文件至手机端,Android系统最低要求是`4.4`<br>
当然,您也可以下载导入`Android Studio`中运行安装,下面着重介绍一下开发配置:
- Android Studio版本:3.1.2
- Gradle版本:4.4
- JDK:使用AS内置jre
- bugly:项目中嵌入了bugly平台的升级和热更新模块,具体使用见[bugly文档中心](https://bugly.qq.com/docs/)
- 期货公司版本:在模块的build.gradle文件中包含多个期货公司版本,您可以在AS的`Build Variant`中选择simnow版本进行测试([simnow账号注册](http://www.simnow.com.cn/))
### Function
软件的主要功能:查看行情以及进行交易<br>
- 首页
- 导航栏:完成各个交易所合约列表的切换以及不同页面的跳转
Expand All @@ -23,30 +31,22 @@
- 持仓:显示账户持仓列表
- 委托:显示账户下单情况
- 交易:三键下单板进行交易
## 安装运行
*快速点亮:* 您可以点击代码仓库的`Release`标签,安装相应版本的apk文件至手机端,Android系统最低要求是`4.4`<br>
当然,您也可以下载导入`Android Studio`中运行安装,下面着重介绍一下开发配置:
- Android Studio版本:3.1.2
- Gradle版本:4.4
- JDK:使用AS内置jre
- bugly:项目中嵌入了bugly平台的升级和热更新模块,具体使用见[bugly文档中心](https://bugly.qq.com/docs/)
- 期货公司版本:在模块的build.gradle文件中包含多个期货公司版本,您可以在AS的`Build Variant`中选择simnow版本进行测试([simnow账号注册](http://www.simnow.com.cn/))
## 代码架构
### 数据层面
### Code Architecture
#### Data
根据websocket协议进行实时数据更新
- 利用OkHttp从服务器获取合约列表文件进行解析
- 利用java-websocket框架分别与行情和交易服务器进行连接,获取行情数据和期货公司列表数据
- 对服务器发过来的json数据进行解析
- 数据解析完毕后利用android广播机制进行行情数据以及交易数据的刷新
### UI层面
#### UI
界面由activity、fragment以及adapter三个模块构成,分别负责作为容器、数据展示与交互以及数据绑定刷新
- 利用Support Library库中的RecyclerView实现合约报价列表、Drawlayout实现抽屉导航
- 利用MPAndroidChart框架完成K线图的绘制
- 利用dataBinding框架部分页面数据的绑定,简化代码
- 自定义下单软键盘
### 主要第三方库
#### Framework
- [Gson](https://github.com/google/gson)
- [EventBus](https://github.com/greenrobot/EventBus)
- [Okhttp](https://github.com/square/okhttp)
- [Java-Websockets](https://github.com/TooTallNate/Java-WebSocket)
- [nv-websocket-client](https://github.com/TakahikoKawasaki/nv-websocket-client)
- [MPAndroidChart](https://github.com/PhilJay/MPAndroidChart)
Original file line number Diff line number Diff line change
Expand Up @@ -400,9 +400,10 @@ public void onReceive(Context context, Intent intent) {
ToastNotificationUtils.showToast(sContext, "行情服务器连接成功");
break;
case CLOSE:
ToastNotificationUtils.showToast(sContext, "行情服务器连接断开,正在重连...");
//每隔两秒,断线重连
if (!mIsBackground) {
ToastNotificationUtils.showToast(sContext, "行情服务器连接断开,正在重连...");

if (NetworkUtils.isNetworkConnected(sContext))
mMyHandler.sendEmptyMessageDelayed(0, 2000);
else
Expand Down Expand Up @@ -436,10 +437,11 @@ public void onReceive(Context context, Intent intent) {
ToastNotificationUtils.showToast(sContext, "交易服务器连接成功");
break;
case CLOSE:
ToastNotificationUtils.showToast(sContext, "交易服务器连接断开,正在重连...");
LoginActivity.setIsLogin(false);
//每隔两秒,断线重连
if (!mIsBackground) {
ToastNotificationUtils.showToast(sContext, "交易服务器连接断开,正在重连...");

if (NetworkUtils.isNetworkConnected(sContext))
mMyHandler.sendEmptyMessageDelayed(1, 2000);
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ public class SearchEntity {
private String vm = "";
private String sort_key = "";
private String margin = "";
private String underlying_symbol = "";

public String getUnderlying_symbol() {
return underlying_symbol;
}

public void setUnderlying_symbol(String underlying_symbol) {
this.underlying_symbol = underlying_symbol;
}

public String getMargin() {
return margin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,25 @@
*/
public class LatestFileManager {

private static Comparator<String> comparator;
private static JSONObject jsonObject ;

private static Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare(String instrumentId1, String instrumentId2) {
JSONObject jsonObject1 = jsonObject.optJSONObject(instrumentId1);
JSONObject jsonObject2 = jsonObject.optJSONObject(instrumentId2);
if (jsonObject1 == null || jsonObject2 == null){
return instrumentId1.compareTo(instrumentId2);
}
int sort_key1 = jsonObject1.optInt("sort_key");
int sort_key2 = jsonObject2.optInt("sort_key");
if (sort_key1 == sort_key2) {
return instrumentId1.compareTo(instrumentId2);
} else {
return sort_key1 - sort_key2;
}
}
};

/**
* date: 7/9/17
Expand Down Expand Up @@ -161,7 +179,7 @@ public class LatestFileManager {
* date: 7/9/17
* description: 搜索列表实例历史
*/
private static Map<String, SearchEntity> SEARCH_ENTITIES_HISTORY = new TreeMap<>(comparator);
private static Map<String, SearchEntity> SEARCH_ENTITIES_HISTORY = new TreeMap<>(comparator);


/**
Expand All @@ -172,33 +190,17 @@ public class LatestFileManager {
public static void initInsList(File latestFile) {
LogUtils.e("合约列表解析开始", true);
String latest = readLatestFile(latestFile.getName());
if (latest == null)return;
if (latest == null) return;
try {
final JSONObject jsonObject = new JSONObject(latest);
comparator = new Comparator<String>() {
@Override
public int compare(String instrumentId1, String instrumentId2) {
try {
int sort_key1 = jsonObject.getJSONObject(instrumentId1).optInt("sort_key");
int sort_key2 = jsonObject.getJSONObject(instrumentId2).optInt("sort_key");
if (sort_key1 == sort_key2){
return instrumentId1.compareTo(instrumentId2);
}else {
return sort_key1 - sort_key2;
}

} catch (JSONException e) {
e.printStackTrace();
}
return instrumentId1.compareTo(instrumentId2);
}
};
jsonObject = new JSONObject(latest);
Iterator<String> instrumentIds = jsonObject.keys();
while (instrumentIds.hasNext()) {
String instrument_id = instrumentIds.next();
JSONObject subObject = jsonObject.getJSONObject(instrument_id);
String classN = subObject.optString("class");
if (!"FUTURE_CONT".equals(classN) && !"FUTURE".equals(classN) && !"FUTURE_COMBINE".equals(classN)){continue;}
if (!"FUTURE_CONT".equals(classN) && !"FUTURE".equals(classN) && !"FUTURE_COMBINE".equals(classN)) {
continue;
}
String ins_name = subObject.optString("ins_name");
String exchange_id = subObject.optString("exchange_id");
String price_tick = subObject.optString("price_tick");
Expand All @@ -216,14 +218,15 @@ public int compare(String instrumentId1, String instrumentId2) {
searchEntity.setSort_key(sort_key);
searchEntity.setPy(py);
searchEntity.setMargin(margin);
LogUtils.e(margin, true);
QuoteEntity quoteEntity = new QuoteEntity();
quoteEntity.setInstrument_id(instrument_id);

if ("FUTURE_CONT".equals(classN)) {
String underlying_symbol = subObject.optString("underlying_symbol");
sMainInsList.put(underlying_symbol, quoteEntity);
sMainInsListNameNav.put(underlying_symbol, ins_name.replace("主连", ""));
if ("".equals(underlying_symbol)) continue;
searchEntity.setUnderlying_symbol(underlying_symbol);
sMainInsList.put(instrument_id, quoteEntity);
sMainInsListNameNav.put(instrument_id, ins_name.replace("主连", ""));
}

if ("FUTURE".equals(classN)) {
Expand Down Expand Up @@ -268,6 +271,7 @@ public int compare(String instrumentId1, String instrumentId2) {
JSONObject subObjectFuture = jsonObject.optJSONObject(leg1_symbol);
String product_short_name_leg = subObjectFuture.optString("product_short_name");
String py_leg = subObjectFuture.optString("py");
searchEntity.setPy(py_leg);
switch (exchange_id) {
case "CZCE":
sZhengzhouzuheInsList.put(instrument_id, quoteEntity);
Expand All @@ -284,7 +288,6 @@ public int compare(String instrumentId1, String instrumentId2) {
default:
break;
}
searchEntity.setPy(py_leg);
}

SEARCH_ENTITIES.put(instrument_id, searchEntity);
Expand Down Expand Up @@ -392,7 +395,7 @@ public static Map<String, SearchEntity> getSearchEntitiesHistory() {
* description: 根据最新价与昨收获取合约涨跌幅
*/
public static String getUpDownRate(String latest, String preClose) {
return MathUtils.round(MathUtils.multiply( MathUtils.divide(MathUtils.subtract(latest, preClose), preClose), "100"), 2, BigDecimal.ROUND_HALF_EVEN);
return MathUtils.round(MathUtils.multiply(MathUtils.divide(MathUtils.subtract(latest, preClose), preClose), "100"), 2, BigDecimal.ROUND_HALF_EVEN);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public void sendPeekMessage() {
if (webSocketClient != null && webSocketClient.getState() == WebSocketState.OPEN) {
String peekMessage = "{\"aid\":\"peek_message\"}";
webSocketClient.sendText(peekMessage);
LogUtils.e("PeekMessage", false);
LogUtils.e(peekMessage, false);
}
}

Expand Down Expand Up @@ -329,12 +329,13 @@ public void onConnected(WebSocket websocket, Map<String, List<String>> headers)

// A text message arrived from the server.
public void onTextMessage(WebSocket websocket, String message) {
LogUtils.e(message, true);
LogUtils.e(message, false);
try {
sDataManager.refreshAccountBean(message);
} catch (JSONException e) {
e.printStackTrace();
}
sendPeekMessageTransaction();
}

@Override
Expand All @@ -359,19 +360,32 @@ public void onError(WebSocket websocket, WebSocketException cause) {
}

public void disConnectTransaction() {
if (webSocketClientTransaction != null && webSocketClient.getState() == WebSocketState.OPEN) {
if (webSocketClientTransaction != null && webSocketClientTransaction.getState() == WebSocketState.OPEN) {
webSocketClientTransaction.disconnect();
webSocketClientTransaction = null;
}
}

/**
* date: 7/9/17
* author: chenli
* description: 获取合约信息
*/
public void sendPeekMessageTransaction() {
if (webSocketClientTransaction != null && webSocketClientTransaction.getState() == WebSocketState.OPEN) {
String peekMessage = "{\"aid\":\"peek_message\"}";
webSocketClientTransaction.sendText(peekMessage);
LogUtils.e(peekMessage, false);
}
}

/**
* date: 7/9/17
* author: chenli
* description: 用户登录
*/
public void sendReqLogin(String bid, String user_name, String password) {
if (webSocketClientTransaction != null && webSocketClient.getState() == WebSocketState.OPEN) {
if (webSocketClientTransaction != null && webSocketClientTransaction.getState() == WebSocketState.OPEN) {
String reqLogin = "{\"aid\":\"req_login\",\"bid\":\"" + bid + "\",\"user_name\":\"" + user_name + "\",\"password\":\"" + password + "\"}";
LogUtils.e(reqLogin, true);
webSocketClientTransaction.sendText(reqLogin);
Expand All @@ -384,7 +398,7 @@ public void sendReqLogin(String bid, String user_name, String password) {
* description: 确认结算单
*/
public void sendReqConfirmSettlement(String req_id, String msg) {
if (webSocketClientTransaction != null && webSocketClient.getState() == WebSocketState.OPEN) {
if (webSocketClientTransaction != null && webSocketClientTransaction.getState() == WebSocketState.OPEN) {
String confirmSettlement = "{\"aid\":\"MobileConfirmSettlement\",\"req_id\":\"" + req_id + "\",\"msg\":\"" + msg + "\"}";
LogUtils.e(confirmSettlement, true);
webSocketClientTransaction.sendText(confirmSettlement);
Expand All @@ -398,8 +412,8 @@ public void sendReqConfirmSettlement(String req_id, String msg) {
* description: 下单
*/
public void sendReqInsertOrder(String order_id, String exchange_id, String instrument_id, String direction, String offset, int volume, String price_type, double price) {
if (webSocketClientTransaction != null && webSocketClient.getState() == WebSocketState.OPEN) {
String reqInsertOrder = "{\"aid\":\"insert_order\",\"order_id\":\"" + order_id + "\",\"exchange_id\":\"" + exchange_id + "\",\"instrument_id\":\"" + instrument_id + "\",\"direction\":\"" + direction + "\",\"offset\":\"" + offset + "\",\"volume\":" + volume + ",\"price_type\":\"" + price_type + "\",\"limit_price\":" + price + "}";
if (webSocketClientTransaction != null && webSocketClientTransaction.getState() == WebSocketState.OPEN) {
String reqInsertOrder = "{\"aid\":\"insert_order\",\"order_id\":\"" + order_id + "\",\"exchange_id\":\"" + exchange_id + "\",\"instrument_id\":\"" + instrument_id + "\",\"direction\":\"" + direction + "\",\"offset\":\"" + offset + "\",\"volume\":" + volume + ",\"price_type\":\"" + price_type + "\",\"limit_price\":" + price + ", \"volume_condition\":\"ANY\", \"time_condition\":\"GFD\"}";
LogUtils.e(reqInsertOrder, true);
webSocketClientTransaction.sendText(reqInsertOrder);
}
Expand All @@ -411,7 +425,7 @@ public void sendReqInsertOrder(String order_id, String exchange_id, String instr
* description: 撤单
*/
public void sendReqCancelOrder(String order_id) {
if (webSocketClientTransaction != null && webSocketClient.getState() == WebSocketState.OPEN) {
if (webSocketClientTransaction != null && webSocketClientTransaction.getState() == WebSocketState.OPEN) {
String reqInsertOrder = "{\"aid\":\"cancel_order\",\"order_id\":\"" + order_id + "\"}";
LogUtils.e(reqInsertOrder, true);
webSocketClientTransaction.sendText(reqInsertOrder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ private void switchPages(int mCurItemId) {
* description: 点击合约导航滑动行情列表
*/
private void scrollQuotes(String title, int position, String instrumentId) {
if (instrumentId.contains("&")){
instrumentId = instrumentId.split("&")[0];
}
instrumentId = instrumentId.replaceAll("\\d", "");
List<String> insListName = new ArrayList<>();
switch (title) {
case DOMINANT:
Expand Down Expand Up @@ -479,7 +483,7 @@ private void scrollQuotes(String title, int position, String instrumentId) {
}

for (int i = 0; i < insListName.size(); i++) {
if (insListName.get(i).equals(instrumentId)) {
if (insListName.get(i).contains(instrumentId)) {
//出现重复的合约中文名,则导航到第一个出现的位置
position = i;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.shinnytech.futures.databinding.ItemActivitySearchQuoteBinding;
import com.shinnytech.futures.model.bean.searchinfobean.SearchEntity;
import com.shinnytech.futures.model.engine.LatestFileManager;
import com.shinnytech.futures.utils.LogUtils;

import java.util.ArrayList;
import java.util.List;
Expand Down
Loading

0 comments on commit e6599e3

Please sign in to comment.